diff --git a/.github/workflows/branchtest.yml b/.github/workflows/branchtest.yml
index d2b22a05..4dafd9d5 100644
--- a/.github/workflows/branchtest.yml
+++ b/.github/workflows/branchtest.yml
@@ -2,6 +2,11 @@ name: Deploy and Test Branch
on:
workflow_dispatch:
+ inputs:
+ branches:
+ description: 'Branch to deploy and test'
+ required: true
+ default: 'develop'
pull_request:
types: [opened, synchronize, reopened]
branches: [develop]
@@ -28,7 +33,7 @@ jobs:
uses: actions/checkout@v4
with:
repository: gravitl/netclient
- ref: develop
+ ref: ${{ github.event_name == 'workflow_dispatch' && inputs.branch || 'develop' }}
- name: check if branch exists
id: getbranch
run: |
@@ -45,6 +50,6 @@ jobs:
needs: [getbranch, skip-check]
with:
netclientbranch: ${{ needs.getbranch.outputs.netclientbranch }}
- netmakerbranch: ${{ github.head_ref }}
+ netmakerbranch: ${{ github.event_name == 'workflow_dispatch' && inputs.branch || github.head_ref }}
tag: ${{ github.run_id }}-${{ github.run_attempt }}
secrets: inherit
diff --git a/.github/workflows/deletedroplets.yml b/.github/workflows/deletedroplets.yml
index 04019038..11577278 100644
--- a/.github/workflows/deletedroplets.yml
+++ b/.github/workflows/deletedroplets.yml
@@ -37,13 +37,28 @@ jobs:
- name: delete droplets
if: success() || failure()
run: |
- sleep 15m
- curl -X DELETE \
+ sleep 1m
+ 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
+ sleep 1m
env:
- DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
+ DIGITALOCEAN_TOKEN: ${{ secrets.DO_TEST_TOKEN }}
TAG: ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}
- name: mark server as available
if: success() || failure()
@@ -94,13 +109,28 @@ jobs:
- name: delete droplets
if: success() || failure()
run: |
- sleep 3h
- curl -X DELETE \
+ sleep 1m
+ 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
+ sleep 1m
env:
- DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
+ DIGITALOCEAN_TOKEN: ${{ secrets.DO_TEST_TOKEN }}
TAG: ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}
- name: mark server as available
if: success() || failure()
diff --git a/controllers/user.go b/controllers/user.go
index 72925bc2..e1166576 100644
--- a/controllers/user.go
+++ b/controllers/user.go
@@ -23,6 +23,8 @@ var (
upgrader = websocket.Upgrader{}
)
+var ListRoles = listRoles
+
func userHandlers(r *mux.Router) {
r.HandleFunc("/api/users/adm/hassuperadmin", hasSuperAdmin).Methods(http.MethodGet)
r.HandleFunc("/api/users/adm/createsuperadmin", createSuperAdmin).Methods(http.MethodPost)
@@ -35,6 +37,7 @@ func userHandlers(r *mux.Router) {
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)
+ r.HandleFunc("/api/v1/users/roles", logic.SecurityCheck(true, http.HandlerFunc(ListRoles))).Methods(http.MethodGet)
}
@@ -407,6 +410,9 @@ func createUser(w http.ResponseWriter, r *http.Request) {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
+ if !servercfg.IsPro {
+ user.PlatformRoleID = models.AdminRole
+ }
if user.PlatformRoleID == "" {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("platform role is missing"), "badrequest"))
@@ -710,3 +716,24 @@ func socketHandler(w http.ResponseWriter, r *http.Request) {
// Start handling the session
go auth.SessionHandler(conn)
}
+
+// @Summary lists all user roles.
+// @Router /api/v1/user/roles [get]
+// @Tags Users
+// @Param role_id param string true "roleid required to get the role details"
+// @Success 200 {object} []models.UserRolePermissionTemplate
+// @Failure 500 {object} models.ErrorResponse
+func listRoles(w http.ResponseWriter, r *http.Request) {
+ var roles []models.UserRolePermissionTemplate
+ var err error
+ roles, err = logic.ListPlatformRoles()
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, models.ErrorResponse{
+ Code: http.StatusInternalServerError,
+ Message: err.Error(),
+ })
+ return
+ }
+
+ logic.ReturnSuccessResponseWithJson(w, r, roles, "successfully fetched user roles permission templates")
+}
diff --git a/go.mod b/go.mod
index 4a59f33f..b5bcfe90 100644
--- a/go.mod
+++ b/go.mod
@@ -38,11 +38,9 @@ require (
)
require (
- github.com/go-jose/go-jose/v3 v3.0.3
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
)
@@ -50,6 +48,7 @@ require (
require (
cloud.google.com/go/compute/metadata v0.3.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
+ github.com/go-jose/go-jose/v3 v3.0.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/seancfoley/bintree v1.3.1 // indirect
diff --git a/go.sum b/go.sum
index 54809822..33d5e376 100644
--- a/go.sum
+++ b/go.sum
@@ -14,8 +14,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik=
github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE=
-github.com/eclipse/paho.mqtt.golang v1.5.0 h1:EH+bUVJNgttidWFkLLVKaQPGmkTUfQQqjOsyvMGvD6o=
-github.com/eclipse/paho.mqtt.golang v1.5.0/go.mod h1:du/2qNQVqJf/Sqs4MEL77kR8QTqANF7XU7Fk0aOTAgk=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
@@ -65,10 +63,6 @@ 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 v0.0.0-20211028072449-93c17c49e2b0 h1:Y2hUrkfuM0on62KZOci/VLijlkdF/yeWU262BQgvcjE=
github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0/go.mod h1:oa2sAs9tGai3VldabTV0eWejt/O4/OOD7azP8GaikqU=
-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=
@@ -102,8 +96,6 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
-golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
-golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -115,8 +107,6 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
-golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
-golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -134,8 +124,6 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
-golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
diff --git a/logic/auth.go b/logic/auth.go
index 7486d310..3da9f605 100644
--- a/logic/auth.go
+++ b/logic/auth.go
@@ -278,6 +278,9 @@ func UpdateUser(userchange, user *models.User) (*models.User, error) {
user.UserName = userchange.UserName
}
if userchange.Password != "" {
+ if len(userchange.Password) < 5 {
+ return &models.User{}, errors.New("password requires min 5 characters")
+ }
// encrypt that password so we never see it again
hash, err := bcrypt.GenerateFromPassword([]byte(userchange.Password), 5)
@@ -306,7 +309,6 @@ func UpdateUser(userchange, user *models.User) (*models.User, error) {
if err != nil {
return &models.User{}, err
}
-
if err = database.DeleteRecord(database.USERS_TABLE_NAME, queryUser); err != nil {
return &models.User{}, err
}
diff --git a/logic/networks.go b/logic/networks.go
index b87c6db8..14ad794e 100644
--- a/logic/networks.go
+++ b/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
+ } else {
+ for _, n := range nodes {
+
+ 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
+ }
+ }
+
}
- for _, n := range nodes {
-
- 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
+ //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)
+ }
}
}
diff --git a/logic/security.go b/logic/security.go
index aa692236..84f7a3cf 100644
--- a/logic/security.go
+++ b/logic/security.go
@@ -6,7 +6,6 @@ import (
"strings"
"github.com/gorilla/mux"
- "github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/servercfg"
)
@@ -27,12 +26,10 @@ 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 := GetUserNameFromToken(bearerToken)
if err != nil {
- logger.Log(0, "next 1", r.URL.String(), err.Error())
ReturnErrorResponse(w, r, FormatError(err, "unauthorized"))
return
}
@@ -103,7 +100,6 @@ func ContinueIfUserMatch(next http.Handler) http.HandlerFunc {
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
}
diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go
index 6f20cd71..8727cb57 100644
--- a/logic/user_mgmt.go
+++ b/logic/user_mgmt.go
@@ -66,6 +66,27 @@ func GetRole(roleID models.UserRoleID) (models.UserRolePermissionTemplate, error
return ur, nil
}
+// ListPlatformRoles - lists user platform roles permission templates
+func ListPlatformRoles() ([]models.UserRolePermissionTemplate, error) {
+ data, err := database.FetchRecords(database.USER_PERMISSIONS_TABLE_NAME)
+ if err != nil && !database.IsEmptyRecord(err) {
+ return []models.UserRolePermissionTemplate{}, err
+ }
+ userRoles := []models.UserRolePermissionTemplate{}
+ for _, dataI := range data {
+ userRole := models.UserRolePermissionTemplate{}
+ err := json.Unmarshal([]byte(dataI), &userRole)
+ if err != nil {
+ continue
+ }
+ if userRole.NetworkID != "" {
+ continue
+ }
+ userRoles = append(userRoles, userRole)
+ }
+ return userRoles, nil
+}
+
func userRolesInit() {
d, _ := json.Marshal(SuperAdminPermissionTemplate)
database.Insert(SuperAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME)
diff --git a/models/user_mgmt.go b/models/user_mgmt.go
index 3efa81bf..a87a0f4b 100644
--- a/models/user_mgmt.go
+++ b/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 {
diff --git a/pro/auth/auth.go b/pro/auth/auth.go
index 1fb0e3d6..49a34e73 100644
--- a/pro/auth/auth.go
+++ b/pro/auth/auth.go
@@ -7,6 +7,7 @@ import (
"strings"
"time"
+ "github.com/golang-jwt/jwt/v4"
"github.com/gorilla/websocket"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
@@ -236,6 +237,17 @@ func getStateAndCode(r *http.Request) (string, string) {
return state, code
}
+func getUserEmailFromClaims(token string) string {
+ accessToken, _ := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
+ return []byte(""), nil
+ })
+ if accessToken == nil {
+ return ""
+ }
+ claims, _ := accessToken.Claims.(jwt.MapClaims)
+ return claims["email"].(string)
+}
+
func (user *OAuthUser) getUserName() string {
var userName string
if user.Email != "" {
diff --git a/pro/auth/azure-ad.go b/pro/auth/azure-ad.go
index b0981aae..7aa34953 100644
--- a/pro/auth/azure-ad.go
+++ b/pro/auth/azure-ad.go
@@ -3,6 +3,7 @@ package auth
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"io"
"net/http"
@@ -33,7 +34,7 @@ func initAzureAD(redirectURL string, clientID string, clientSecret string) {
RedirectURL: redirectURL,
ClientID: clientID,
ClientSecret: clientSecret,
- Scopes: []string{"User.Read"},
+ Scopes: []string{"User.Read", "email", "profile", "openid"},
Endpoint: microsoft.AzureADEndpoint(servercfg.GetAzureTenant()),
}
}
@@ -60,27 +61,37 @@ 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
}
handleOauthNotConfigured(w)
return
}
-
var inviteExists bool
// check if invite exists for User
- in, err := logic.GetUserInvite(content.UserPrincipalName)
+ in, err := logic.GetUserInvite(content.Email)
if err == nil {
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 {
@@ -90,19 +101,20 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
+ user.ExternalIdentityProviderID = content.UserPrincipalName
if err = logic.CreateUser(&user); err != nil {
handleSomethingWentWrong(w)
return
}
- logic.DeleteUserInvite(user.UserName)
- logic.DeletePendingUser(content.UserPrincipalName)
+ logic.DeleteUserInvite(content.Email)
+ 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) {
@@ -166,8 +178,9 @@ func getAzureUserInfo(state string, code string) (*OAuthUser, error) {
}
var httpReq, reqErr = http.NewRequest("GET", "https://graph.microsoft.com/v1.0/me", nil)
if reqErr != nil {
- return nil, fmt.Errorf("failed to create request to GitHub")
+ return nil, fmt.Errorf("failed to create request to microsoft")
}
+
httpReq.Header.Set("Authorization", "Bearer "+token.AccessToken)
response, err := http.DefaultClient.Do(httpReq)
if err != nil {
@@ -183,6 +196,13 @@ func getAzureUserInfo(state string, code string) (*OAuthUser, error) {
return nil, fmt.Errorf("failed parsing email from response data: %s", err.Error())
}
userInfo.AccessToken = string(data)
+ 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
}
diff --git a/pro/auth/error.go b/pro/auth/error.go
index b2a5efcd..eb49b876 100644
--- a/pro/auth/error.go
+++ b/pro/auth/error.go
@@ -18,7 +18,7 @@ var htmlBaseTemplate = `