mirror of
https://github.com/gravitl/netmaker.git
synced 2025-09-06 21:24:16 +08:00
Merge branch 'release-v1.0.0' into NM-96-v1
This commit is contained in:
commit
a670bdb18a
8 changed files with 201 additions and 70 deletions
|
@ -38,6 +38,24 @@ func UpsertServerSettings(s models.ServerSettings) error {
|
|||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -3,6 +3,10 @@ package auth
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gravitl/netmaker/database"
|
||||
"github.com/gravitl/netmaker/logger"
|
||||
"github.com/gravitl/netmaker/logic"
|
||||
|
@ -12,9 +16,6 @@ import (
|
|||
"github.com/gravitl/netmaker/pro/idp/google"
|
||||
"github.com/gravitl/netmaker/pro/idp/okta"
|
||||
proLogic "github.com/gravitl/netmaker/pro/logic"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -85,15 +86,23 @@ func SyncFromIDP() error {
|
|||
}
|
||||
|
||||
if settings.AuthProvider != "" && idpClient != nil {
|
||||
idpUsers, err = idpClient.GetUsers()
|
||||
idpUsers, err = idpClient.GetUsers(settings.UserFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
idpGroups, err = idpClient.GetGroups()
|
||||
idpGroups, err = idpClient.GetGroups(settings.GroupFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(settings.GroupFilters) > 0 {
|
||||
idpUsers = filterUsersByGroupMembership(idpUsers, idpGroups)
|
||||
}
|
||||
|
||||
if len(settings.UserFilters) > 0 {
|
||||
idpGroups = filterGroupsByMembers(idpGroups, idpUsers)
|
||||
}
|
||||
}
|
||||
|
||||
err = syncUsers(idpUsers)
|
||||
|
@ -316,3 +325,64 @@ func syncGroups(idpGroups []idp.Group) error {
|
|||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -4,10 +4,11 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gravitl/netmaker/logic"
|
||||
"github.com/gravitl/netmaker/pro/idp"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/gravitl/netmaker/logic"
|
||||
"github.com/gravitl/netmaker/pro/idp"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
|
@ -26,89 +27,103 @@ func NewAzureEntraIDClient() *Client {
|
|||
}
|
||||
}
|
||||
|
||||
func (a *Client) GetUsers() ([]idp.User, error) {
|
||||
func (a *Client) GetUsers(filters []string) ([]idp.User, error) {
|
||||
accessToken, err := a.getAccessToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "https://graph.microsoft.com/v1.0/users?$select=id,userPrincipalName,displayName,accountEnabled", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
getUsersURL := "https://graph.microsoft.com/v1.0/users?$select=id,userPrincipalName,displayName,accountEnabled"
|
||||
if len(filters) > 0 {
|
||||
getUsersURL += "&" + buildPrefixFilter("userPrincipalName", filters)
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "Bearer "+accessToken)
|
||||
req.Header.Add("Accept", "application/json")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
var users getUsersResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
retval := make([]idp.User, len(users.Value))
|
||||
for i, user := range users.Value {
|
||||
retval[i] = idp.User{
|
||||
ID: user.Id,
|
||||
Username: user.UserPrincipalName,
|
||||
DisplayName: user.DisplayName,
|
||||
AccountDisabled: !user.AccountEnabled,
|
||||
var retval []idp.User
|
||||
for getUsersURL != "" {
|
||||
req, err := http.NewRequest("GET", getUsersURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "Bearer "+accessToken)
|
||||
req.Header.Add("Accept", "application/json")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var users getUsersResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&users)
|
||||
_ = resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, user := range users.Value {
|
||||
retval = append(retval, idp.User{
|
||||
ID: user.Id,
|
||||
Username: user.UserPrincipalName,
|
||||
DisplayName: user.DisplayName,
|
||||
AccountDisabled: !user.AccountEnabled,
|
||||
})
|
||||
}
|
||||
|
||||
getUsersURL = users.NextLink
|
||||
}
|
||||
|
||||
return retval, nil
|
||||
}
|
||||
|
||||
func (a *Client) GetGroups() ([]idp.Group, error) {
|
||||
func (a *Client) GetGroups(filters []string) ([]idp.Group, error) {
|
||||
accessToken, err := a.getAccessToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "https://graph.microsoft.com/v1.0/groups?$select=id,displayName&$expand=members($select=id)", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
getGroupsURL := "https://graph.microsoft.com/v1.0/groups?$select=id,displayName&$expand=members($select=id)"
|
||||
if len(filters) > 0 {
|
||||
getGroupsURL += "&" + buildPrefixFilter("displayName", filters)
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "Bearer "+accessToken)
|
||||
req.Header.Add("Accept", "application/json")
|
||||
var retval []idp.Group
|
||||
for getGroupsURL != "" {
|
||||
req, err := http.NewRequest("GET", getGroupsURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
req.Header.Add("Authorization", "Bearer "+accessToken)
|
||||
req.Header.Add("Accept", "application/json")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var groups getGroupsResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&groups)
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
var groups getGroupsResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&groups)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
retval := make([]idp.Group, len(groups.Value))
|
||||
for i, group := range groups.Value {
|
||||
retvalMembers := make([]string, len(group.Members))
|
||||
for j, member := range group.Members {
|
||||
retvalMembers[j] = member.Id
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
retval[i] = idp.Group{
|
||||
ID: group.Id,
|
||||
Name: group.DisplayName,
|
||||
Members: retvalMembers,
|
||||
for _, group := range groups.Value {
|
||||
retvalMembers := make([]string, len(group.Members))
|
||||
for j, member := range group.Members {
|
||||
retvalMembers[j] = member.Id
|
||||
}
|
||||
|
||||
retval = append(retval, idp.Group{
|
||||
ID: group.Id,
|
||||
Name: group.DisplayName,
|
||||
Members: retvalMembers,
|
||||
})
|
||||
}
|
||||
|
||||
getGroupsURL = groups.NextLink
|
||||
}
|
||||
|
||||
return retval, nil
|
||||
|
@ -144,6 +159,18 @@ func (a *Client) getAccessToken() (string, error) {
|
|||
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 {
|
||||
OdataContext string `json:"@odata.context"`
|
||||
Value []struct {
|
||||
|
@ -152,6 +179,7 @@ type getUsersResponse struct {
|
|||
DisplayName string `json:"displayName"`
|
||||
AccountEnabled bool `json:"accountEnabled"`
|
||||
} `json:"value"`
|
||||
NextLink string `json:"@odata.nextLink"`
|
||||
}
|
||||
|
||||
type getGroupsResponse struct {
|
||||
|
@ -164,4 +192,5 @@ type getGroupsResponse struct {
|
|||
Id string `json:"id"`
|
||||
} `json:"members"`
|
||||
} `json:"value"`
|
||||
NextLink string `json:"@odata.nextLink"`
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/gravitl/netmaker/logic"
|
||||
"github.com/gravitl/netmaker/pro/idp"
|
||||
admindir "google.golang.org/api/admin/directory/v1"
|
||||
|
@ -59,7 +60,7 @@ func NewGoogleWorkspaceClient() (*Client, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (g *Client) GetUsers() ([]idp.User, error) {
|
||||
func (g *Client) GetUsers(filters []string) ([]idp.User, error) {
|
||||
var retval []idp.User
|
||||
err := g.service.Users.List().
|
||||
Customer("my_customer").
|
||||
|
@ -81,7 +82,7 @@ func (g *Client) GetUsers() ([]idp.User, error) {
|
|||
return retval, err
|
||||
}
|
||||
|
||||
func (g *Client) GetGroups() ([]idp.Group, error) {
|
||||
func (g *Client) GetGroups(filters []string) ([]idp.Group, error) {
|
||||
var retval []idp.Group
|
||||
err := g.service.Groups.List().
|
||||
Customer("my_customer").
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package idp
|
||||
|
||||
type Client interface {
|
||||
GetUsers() ([]User, error)
|
||||
GetGroups() ([]Group, error)
|
||||
GetUsers(filters []string) ([]User, error)
|
||||
GetGroups(filters []string) ([]Group, error)
|
||||
}
|
||||
|
||||
type User struct {
|
||||
|
|
|
@ -3,6 +3,7 @@ package okta
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gravitl/netmaker/logic"
|
||||
"github.com/gravitl/netmaker/pro/idp"
|
||||
"github.com/okta/okta-sdk-golang/v5/okta"
|
||||
|
@ -42,7 +43,7 @@ func (o *Client) Verify() error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (o *Client) GetUsers() ([]idp.User, error) {
|
||||
func (o *Client) GetUsers(filters []string) ([]idp.User, error) {
|
||||
var retval []idp.User
|
||||
var allUsersFetched bool
|
||||
|
||||
|
@ -81,7 +82,7 @@ func (o *Client) GetUsers() ([]idp.User, error) {
|
|||
return retval, nil
|
||||
}
|
||||
|
||||
func (o *Client) GetGroups() ([]idp.Group, error) {
|
||||
func (o *Client) GetGroups(filters []string) ([]idp.Group, error) {
|
||||
var retval []idp.Group
|
||||
var allGroupsFetched bool
|
||||
|
||||
|
|
|
@ -449,6 +449,15 @@ func ListUserPolicies(u models.User) []models.Acl {
|
|||
func listPoliciesOfUser(user models.User, netID models.NetworkID) []models.Acl {
|
||||
allAcls := logic.ListAcls()
|
||||
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 {
|
||||
if acl.NetworkID == netID && acl.RuleType == models.UserPolicy {
|
||||
srcMap := logic.ConvAclTagToValueMap(acl.Src)
|
||||
|
|
|
@ -729,7 +729,10 @@ func GetUserRAGNodes(user models.User) (gws map[string]models.Node) {
|
|||
continue
|
||||
}
|
||||
if user.PlatformRoleID == models.AdminRole || user.PlatformRoleID == models.SuperAdminRole {
|
||||
gws[node.ID.String()] = node
|
||||
if ok, _ := IsUserAllowedToCommunicate(user.UserName, node); ok {
|
||||
gws[node.ID.String()] = node
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// check if user has network role assigned
|
||||
if roles, ok := user.NetworkRoles[models.NetworkID(node.Network)]; ok && len(roles) > 0 {
|
||||
|
|
Loading…
Add table
Reference in a new issue