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 = `