Merge branch 'release-v1.0.0' into NM-96-v1

This commit is contained in:
Abhishek K 2025-08-28 20:13:54 +05:30 committed by GitHub
commit a670bdb18a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 201 additions and 70 deletions

View file

@ -38,6 +38,24 @@ func UpsertServerSettings(s models.ServerSettings) error {
s.BasicAuth = true s.BasicAuth = true
} }
var userFilters []string
for _, userFilter := range s.UserFilters {
userFilter = strings.TrimSpace(userFilter)
if userFilter != "" {
userFilters = append(userFilters, userFilter)
}
}
s.UserFilters = userFilters
var groupFilters []string
for _, groupFilter := range s.GroupFilters {
groupFilter = strings.TrimSpace(groupFilter)
if groupFilter != "" {
groupFilters = append(groupFilters, groupFilter)
}
}
s.GroupFilters = groupFilters
data, err := json.Marshal(s) data, err := json.Marshal(s)
if err != nil { if err != nil {
return err return err

View file

@ -3,6 +3,10 @@ package auth
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"sync"
"time"
"github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/logic"
@ -12,9 +16,6 @@ import (
"github.com/gravitl/netmaker/pro/idp/google" "github.com/gravitl/netmaker/pro/idp/google"
"github.com/gravitl/netmaker/pro/idp/okta" "github.com/gravitl/netmaker/pro/idp/okta"
proLogic "github.com/gravitl/netmaker/pro/logic" proLogic "github.com/gravitl/netmaker/pro/logic"
"strings"
"sync"
"time"
) )
var ( var (
@ -85,15 +86,23 @@ func SyncFromIDP() error {
} }
if settings.AuthProvider != "" && idpClient != nil { if settings.AuthProvider != "" && idpClient != nil {
idpUsers, err = idpClient.GetUsers() idpUsers, err = idpClient.GetUsers(settings.UserFilters)
if err != nil { if err != nil {
return err return err
} }
idpGroups, err = idpClient.GetGroups() idpGroups, err = idpClient.GetGroups(settings.GroupFilters)
if err != nil { if err != nil {
return err return err
} }
if len(settings.GroupFilters) > 0 {
idpUsers = filterUsersByGroupMembership(idpUsers, idpGroups)
}
if len(settings.UserFilters) > 0 {
idpGroups = filterGroupsByMembers(idpGroups, idpUsers)
}
} }
err = syncUsers(idpUsers) err = syncUsers(idpUsers)
@ -316,3 +325,64 @@ func syncGroups(idpGroups []idp.Group) error {
return nil return nil
} }
func filterUsersByGroupMembership(idpUsers []idp.User, idpGroups []idp.Group) []idp.User {
usersMap := make(map[string]int)
for i, user := range idpUsers {
usersMap[user.ID] = i
}
filteredUsersMap := make(map[string]int)
for _, group := range idpGroups {
for _, member := range group.Members {
if userIdx, ok := usersMap[member]; ok {
// user at index `userIdx` is a member of at least one of the
// groups in the `idpGroups` list, so we keep it.
filteredUsersMap[member] = userIdx
}
}
}
i := 0
filteredUsers := make([]idp.User, len(filteredUsersMap))
for _, userIdx := range filteredUsersMap {
filteredUsers[i] = idpUsers[userIdx]
i++
}
return filteredUsers
}
func filterGroupsByMembers(idpGroups []idp.Group, idpUsers []idp.User) []idp.Group {
usersMap := make(map[string]int)
for i, user := range idpUsers {
usersMap[user.ID] = i
}
filteredGroupsMap := make(map[int]bool)
for i, group := range idpGroups {
var members []string
for _, member := range group.Members {
if _, ok := usersMap[member]; ok {
members = append(members, member)
}
if len(members) > 0 {
// the group at index `i` has members from the `idpUsers` list,
// so we keep it.
filteredGroupsMap[i] = true
// filter out members that were not provided in the `idpUsers` list.
idpGroups[i].Members = members
}
}
}
i := 0
filteredGroups := make([]idp.Group, len(filteredGroupsMap))
for groupIdx := range filteredGroupsMap {
filteredGroups[i] = idpGroups[groupIdx]
i++
}
return filteredGroups
}

View file

@ -4,10 +4,11 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/pro/idp"
"net/http" "net/http"
"net/url" "net/url"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/pro/idp"
) )
type Client struct { type Client struct {
@ -26,14 +27,21 @@ func NewAzureEntraIDClient() *Client {
} }
} }
func (a *Client) GetUsers() ([]idp.User, error) { func (a *Client) GetUsers(filters []string) ([]idp.User, error) {
accessToken, err := a.getAccessToken() accessToken, err := a.getAccessToken()
if err != nil { if err != nil {
return nil, err return nil, err
} }
client := &http.Client{} client := &http.Client{}
req, err := http.NewRequest("GET", "https://graph.microsoft.com/v1.0/users?$select=id,userPrincipalName,displayName,accountEnabled", nil) getUsersURL := "https://graph.microsoft.com/v1.0/users?$select=id,userPrincipalName,displayName,accountEnabled"
if len(filters) > 0 {
getUsersURL += "&" + buildPrefixFilter("userPrincipalName", filters)
}
var retval []idp.User
for getUsersURL != "" {
req, err := http.NewRequest("GET", getUsersURL, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -45,37 +53,44 @@ func (a *Client) GetUsers() ([]idp.User, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer func() {
_ = resp.Body.Close()
}()
var users getUsersResponse var users getUsersResponse
err = json.NewDecoder(resp.Body).Decode(&users) err = json.NewDecoder(resp.Body).Decode(&users)
_ = resp.Body.Close()
if err != nil { if err != nil {
return nil, err return nil, err
} }
retval := make([]idp.User, len(users.Value)) for _, user := range users.Value {
for i, user := range users.Value { retval = append(retval, idp.User{
retval[i] = idp.User{
ID: user.Id, ID: user.Id,
Username: user.UserPrincipalName, Username: user.UserPrincipalName,
DisplayName: user.DisplayName, DisplayName: user.DisplayName,
AccountDisabled: !user.AccountEnabled, AccountDisabled: !user.AccountEnabled,
})
} }
getUsersURL = users.NextLink
} }
return retval, nil return retval, nil
} }
func (a *Client) GetGroups() ([]idp.Group, error) { func (a *Client) GetGroups(filters []string) ([]idp.Group, error) {
accessToken, err := a.getAccessToken() accessToken, err := a.getAccessToken()
if err != nil { if err != nil {
return nil, err return nil, err
} }
client := &http.Client{} client := &http.Client{}
req, err := http.NewRequest("GET", "https://graph.microsoft.com/v1.0/groups?$select=id,displayName&$expand=members($select=id)", nil) getGroupsURL := "https://graph.microsoft.com/v1.0/groups?$select=id,displayName&$expand=members($select=id)"
if len(filters) > 0 {
getGroupsURL += "&" + buildPrefixFilter("displayName", filters)
}
var retval []idp.Group
for getGroupsURL != "" {
req, err := http.NewRequest("GET", getGroupsURL, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -87,28 +102,28 @@ func (a *Client) GetGroups() ([]idp.Group, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer func() {
_ = resp.Body.Close()
}()
var groups getGroupsResponse var groups getGroupsResponse
err = json.NewDecoder(resp.Body).Decode(&groups) err = json.NewDecoder(resp.Body).Decode(&groups)
_ = resp.Body.Close()
if err != nil { if err != nil {
return nil, err return nil, err
} }
retval := make([]idp.Group, len(groups.Value)) for _, group := range groups.Value {
for i, group := range groups.Value {
retvalMembers := make([]string, len(group.Members)) retvalMembers := make([]string, len(group.Members))
for j, member := range group.Members { for j, member := range group.Members {
retvalMembers[j] = member.Id retvalMembers[j] = member.Id
} }
retval[i] = idp.Group{ retval = append(retval, idp.Group{
ID: group.Id, ID: group.Id,
Name: group.DisplayName, Name: group.DisplayName,
Members: retvalMembers, Members: retvalMembers,
})
} }
getGroupsURL = groups.NextLink
} }
return retval, nil return retval, nil
@ -144,6 +159,18 @@ func (a *Client) getAccessToken() (string, error) {
return "", errors.New("failed to get access token") return "", errors.New("failed to get access token")
} }
func buildPrefixFilter(field string, prefixes []string) string {
if len(prefixes) == 0 {
return ""
}
if len(prefixes) == 1 {
return fmt.Sprintf("$filter=startswith(%s,'%s')", field, prefixes[0])
}
return buildPrefixFilter(field, prefixes[1:]) + fmt.Sprintf("%%20or%%20startswith(%s,'%s')", field, prefixes[0])
}
type getUsersResponse struct { type getUsersResponse struct {
OdataContext string `json:"@odata.context"` OdataContext string `json:"@odata.context"`
Value []struct { Value []struct {
@ -152,6 +179,7 @@ type getUsersResponse struct {
DisplayName string `json:"displayName"` DisplayName string `json:"displayName"`
AccountEnabled bool `json:"accountEnabled"` AccountEnabled bool `json:"accountEnabled"`
} `json:"value"` } `json:"value"`
NextLink string `json:"@odata.nextLink"`
} }
type getGroupsResponse struct { type getGroupsResponse struct {
@ -164,4 +192,5 @@ type getGroupsResponse struct {
Id string `json:"id"` Id string `json:"id"`
} `json:"members"` } `json:"members"`
} `json:"value"` } `json:"value"`
NextLink string `json:"@odata.nextLink"`
} }

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/pro/idp" "github.com/gravitl/netmaker/pro/idp"
admindir "google.golang.org/api/admin/directory/v1" admindir "google.golang.org/api/admin/directory/v1"
@ -59,7 +60,7 @@ func NewGoogleWorkspaceClient() (*Client, error) {
}, nil }, nil
} }
func (g *Client) GetUsers() ([]idp.User, error) { func (g *Client) GetUsers(filters []string) ([]idp.User, error) {
var retval []idp.User var retval []idp.User
err := g.service.Users.List(). err := g.service.Users.List().
Customer("my_customer"). Customer("my_customer").
@ -81,7 +82,7 @@ func (g *Client) GetUsers() ([]idp.User, error) {
return retval, err return retval, err
} }
func (g *Client) GetGroups() ([]idp.Group, error) { func (g *Client) GetGroups(filters []string) ([]idp.Group, error) {
var retval []idp.Group var retval []idp.Group
err := g.service.Groups.List(). err := g.service.Groups.List().
Customer("my_customer"). Customer("my_customer").

View file

@ -1,8 +1,8 @@
package idp package idp
type Client interface { type Client interface {
GetUsers() ([]User, error) GetUsers(filters []string) ([]User, error)
GetGroups() ([]Group, error) GetGroups(filters []string) ([]Group, error)
} }
type User struct { type User struct {

View file

@ -3,6 +3,7 @@ package okta
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/pro/idp" "github.com/gravitl/netmaker/pro/idp"
"github.com/okta/okta-sdk-golang/v5/okta" "github.com/okta/okta-sdk-golang/v5/okta"
@ -42,7 +43,7 @@ func (o *Client) Verify() error {
return err return err
} }
func (o *Client) GetUsers() ([]idp.User, error) { func (o *Client) GetUsers(filters []string) ([]idp.User, error) {
var retval []idp.User var retval []idp.User
var allUsersFetched bool var allUsersFetched bool
@ -81,7 +82,7 @@ func (o *Client) GetUsers() ([]idp.User, error) {
return retval, nil return retval, nil
} }
func (o *Client) GetGroups() ([]idp.Group, error) { func (o *Client) GetGroups(filters []string) ([]idp.Group, error) {
var retval []idp.Group var retval []idp.Group
var allGroupsFetched bool var allGroupsFetched bool

View file

@ -449,6 +449,15 @@ func ListUserPolicies(u models.User) []models.Acl {
func listPoliciesOfUser(user models.User, netID models.NetworkID) []models.Acl { func listPoliciesOfUser(user models.User, netID models.NetworkID) []models.Acl {
allAcls := logic.ListAcls() allAcls := logic.ListAcls()
userAcls := []models.Acl{} userAcls := []models.Acl{}
if _, ok := user.UserGroups[globalNetworksAdminGroupID]; ok {
user.UserGroups[GetDefaultNetworkAdminGroupID(netID)] = struct{}{}
}
if _, ok := user.UserGroups[globalNetworksUserGroupID]; ok {
user.UserGroups[GetDefaultNetworkUserGroupID(netID)] = struct{}{}
}
if user.PlatformRoleID == models.AdminRole || user.PlatformRoleID == models.SuperAdminRole {
user.UserGroups[GetDefaultNetworkAdminGroupID(netID)] = struct{}{}
}
for _, acl := range allAcls { for _, acl := range allAcls {
if acl.NetworkID == netID && acl.RuleType == models.UserPolicy { if acl.NetworkID == netID && acl.RuleType == models.UserPolicy {
srcMap := logic.ConvAclTagToValueMap(acl.Src) srcMap := logic.ConvAclTagToValueMap(acl.Src)

View file

@ -729,7 +729,10 @@ func GetUserRAGNodes(user models.User) (gws map[string]models.Node) {
continue continue
} }
if user.PlatformRoleID == models.AdminRole || user.PlatformRoleID == models.SuperAdminRole { if user.PlatformRoleID == models.AdminRole || user.PlatformRoleID == models.SuperAdminRole {
if ok, _ := IsUserAllowedToCommunicate(user.UserName, node); ok {
gws[node.ID.String()] = node gws[node.ID.String()] = node
continue
}
} else { } else {
// check if user has network role assigned // check if user has network role assigned
if roles, ok := user.NetworkRoles[models.NetworkID(node.Network)]; ok && len(roles) > 0 { if roles, ok := user.NetworkRoles[models.NetworkID(node.Network)]; ok && len(roles) > 0 {