mirror of
https://github.com/gravitl/netmaker.git
synced 2025-09-10 15:14:22 +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
|
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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -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").
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Add table
Reference in a new issue