Skip to content

Commit d0a3467

Browse files
authored
Merge pull request #3618 from gravitl/NM-102
NM-102: IDP Filter Improvements
2 parents b4c225b + 1bfd083 commit d0a3467

File tree

6 files changed

+184
-65
lines changed

6 files changed

+184
-65
lines changed

logic/settings.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,24 @@ func UpsertServerSettings(s models.ServerSettings) error {
3838
s.BasicAuth = true
3939
}
4040

41+
var userFilters []string
42+
for _, userFilter := range s.UserFilters {
43+
userFilter = strings.TrimSpace(userFilter)
44+
if userFilter != "" {
45+
userFilters = append(userFilters, userFilter)
46+
}
47+
}
48+
s.UserFilters = userFilters
49+
50+
var groupFilters []string
51+
for _, groupFilter := range s.GroupFilters {
52+
groupFilter = strings.TrimSpace(groupFilter)
53+
if groupFilter != "" {
54+
groupFilters = append(groupFilters, groupFilter)
55+
}
56+
}
57+
s.GroupFilters = groupFilters
58+
4159
data, err := json.Marshal(s)
4260
if err != nil {
4361
return err

pro/auth/sync.go

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ package auth
33
import (
44
"context"
55
"fmt"
6+
"strings"
7+
"sync"
8+
"time"
9+
610
"github.com/gravitl/netmaker/database"
711
"github.com/gravitl/netmaker/logger"
812
"github.com/gravitl/netmaker/logic"
@@ -12,9 +16,6 @@ import (
1216
"github.com/gravitl/netmaker/pro/idp/google"
1317
"github.com/gravitl/netmaker/pro/idp/okta"
1418
proLogic "github.com/gravitl/netmaker/pro/logic"
15-
"strings"
16-
"sync"
17-
"time"
1819
)
1920

2021
var (
@@ -85,15 +86,23 @@ func SyncFromIDP() error {
8586
}
8687

8788
if settings.AuthProvider != "" && idpClient != nil {
88-
idpUsers, err = idpClient.GetUsers()
89+
idpUsers, err = idpClient.GetUsers(settings.UserFilters)
8990
if err != nil {
9091
return err
9192
}
9293

93-
idpGroups, err = idpClient.GetGroups()
94+
idpGroups, err = idpClient.GetGroups(settings.GroupFilters)
9495
if err != nil {
9596
return err
9697
}
98+
99+
if len(settings.GroupFilters) > 0 {
100+
idpUsers = filterUsersByGroupMembership(idpUsers, idpGroups)
101+
}
102+
103+
if len(settings.UserFilters) > 0 {
104+
idpGroups = filterGroupsByMembers(idpGroups, idpUsers)
105+
}
97106
}
98107

99108
err = syncUsers(idpUsers)
@@ -316,3 +325,64 @@ func syncGroups(idpGroups []idp.Group) error {
316325

317326
return nil
318327
}
328+
329+
func filterUsersByGroupMembership(idpUsers []idp.User, idpGroups []idp.Group) []idp.User {
330+
usersMap := make(map[string]int)
331+
for i, user := range idpUsers {
332+
usersMap[user.Username] = i
333+
}
334+
335+
filteredUsersMap := make(map[string]int)
336+
for _, group := range idpGroups {
337+
for _, member := range group.Members {
338+
if userIdx, ok := usersMap[member]; ok {
339+
// user at index `userIdx` is a member of at least one of the
340+
// groups in the `idpGroups` list, so we keep it.
341+
filteredUsersMap[member] = userIdx
342+
}
343+
}
344+
}
345+
346+
i := 0
347+
filteredUsers := make([]idp.User, len(filteredUsersMap))
348+
for _, userIdx := range filteredUsersMap {
349+
filteredUsers[i] = idpUsers[userIdx]
350+
i++
351+
}
352+
353+
return filteredUsers
354+
}
355+
356+
func filterGroupsByMembers(idpGroups []idp.Group, idpUsers []idp.User) []idp.Group {
357+
usersMap := make(map[string]int)
358+
for i, user := range idpUsers {
359+
usersMap[user.Username] = i
360+
}
361+
362+
filteredGroupsMap := make(map[int]bool)
363+
for i, group := range idpGroups {
364+
var members []string
365+
for _, member := range group.Members {
366+
if _, ok := usersMap[member]; ok {
367+
members = append(members, member)
368+
}
369+
370+
if len(members) > 0 {
371+
// the group at index `i` has members from the `idpUsers` list,
372+
// so we keep it.
373+
filteredGroupsMap[i] = true
374+
// filter out members that were not provided in the `idpUsers` list.
375+
idpGroups[i].Members = members
376+
}
377+
}
378+
}
379+
380+
i := 0
381+
filteredGroups := make([]idp.Group, len(filteredGroupsMap))
382+
for groupIdx := range filteredGroupsMap {
383+
filteredGroups[i] = idpGroups[groupIdx]
384+
i++
385+
}
386+
387+
return filteredGroups
388+
}

pro/idp/azure/azure.go

Lines changed: 83 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import (
44
"encoding/json"
55
"errors"
66
"fmt"
7-
"github.com/gravitl/netmaker/logic"
8-
"github.com/gravitl/netmaker/pro/idp"
97
"net/http"
108
"net/url"
9+
10+
"github.com/gravitl/netmaker/logic"
11+
"github.com/gravitl/netmaker/pro/idp"
1112
)
1213

1314
type Client struct {
@@ -26,89 +27,103 @@ func NewAzureEntraIDClient() *Client {
2627
}
2728
}
2829

29-
func (a *Client) GetUsers() ([]idp.User, error) {
30+
func (a *Client) GetUsers(filters []string) ([]idp.User, error) {
3031
accessToken, err := a.getAccessToken()
3132
if err != nil {
3233
return nil, err
3334
}
3435

3536
client := &http.Client{}
36-
req, err := http.NewRequest("GET", "https://graph.microsoft.com/v1.0/users?$select=id,userPrincipalName,displayName,accountEnabled", nil)
37-
if err != nil {
38-
return nil, err
37+
getUsersURL := "https://graph.microsoft.com/v1.0/users?$select=id,userPrincipalName,displayName,accountEnabled"
38+
if len(filters) > 0 {
39+
getUsersURL += "&" + buildPrefixFilter("userPrincipalName", filters)
3940
}
4041

41-
req.Header.Add("Authorization", "Bearer "+accessToken)
42-
req.Header.Add("Accept", "application/json")
42+
var retval []idp.User
43+
for getUsersURL != "" {
44+
req, err := http.NewRequest("GET", getUsersURL, nil)
45+
if err != nil {
46+
return nil, err
47+
}
4348

44-
resp, err := client.Do(req)
45-
if err != nil {
46-
return nil, err
47-
}
48-
defer func() {
49-
_ = resp.Body.Close()
50-
}()
49+
req.Header.Add("Authorization", "Bearer "+accessToken)
50+
req.Header.Add("Accept", "application/json")
5151

52-
var users getUsersResponse
53-
err = json.NewDecoder(resp.Body).Decode(&users)
54-
if err != nil {
55-
return nil, err
56-
}
52+
resp, err := client.Do(req)
53+
if err != nil {
54+
return nil, err
55+
}
5756

58-
retval := make([]idp.User, len(users.Value))
59-
for i, user := range users.Value {
60-
retval[i] = idp.User{
61-
ID: user.Id,
62-
Username: user.UserPrincipalName,
63-
DisplayName: user.DisplayName,
64-
AccountDisabled: !user.AccountEnabled,
57+
var users getUsersResponse
58+
err = json.NewDecoder(resp.Body).Decode(&users)
59+
_ = resp.Body.Close()
60+
if err != nil {
61+
return nil, err
6562
}
63+
64+
for _, user := range users.Value {
65+
retval = append(retval, idp.User{
66+
ID: user.Id,
67+
Username: user.UserPrincipalName,
68+
DisplayName: user.DisplayName,
69+
AccountDisabled: !user.AccountEnabled,
70+
})
71+
}
72+
73+
getUsersURL = users.NextLink
6674
}
6775

6876
return retval, nil
6977
}
7078

71-
func (a *Client) GetGroups() ([]idp.Group, error) {
79+
func (a *Client) GetGroups(filters []string) ([]idp.Group, error) {
7280
accessToken, err := a.getAccessToken()
7381
if err != nil {
7482
return nil, err
7583
}
7684

7785
client := &http.Client{}
78-
req, err := http.NewRequest("GET", "https://graph.microsoft.com/v1.0/groups?$select=id,displayName&$expand=members($select=id)", nil)
79-
if err != nil {
80-
return nil, err
86+
getGroupsURL := "https://graph.microsoft.com/v1.0/groups?$select=id,displayName&$expand=members($select=id)"
87+
if len(filters) > 0 {
88+
getGroupsURL += "&" + buildPrefixFilter("displayName", filters)
8189
}
8290

83-
req.Header.Add("Authorization", "Bearer "+accessToken)
84-
req.Header.Add("Accept", "application/json")
91+
var retval []idp.Group
92+
for getGroupsURL != "" {
93+
req, err := http.NewRequest("GET", getGroupsURL, nil)
94+
if err != nil {
95+
return nil, err
96+
}
8597

86-
resp, err := client.Do(req)
87-
if err != nil {
88-
return nil, err
89-
}
90-
defer func() {
91-
_ = resp.Body.Close()
92-
}()
98+
req.Header.Add("Authorization", "Bearer "+accessToken)
99+
req.Header.Add("Accept", "application/json")
93100

94-
var groups getGroupsResponse
95-
err = json.NewDecoder(resp.Body).Decode(&groups)
96-
if err != nil {
97-
return nil, err
98-
}
101+
resp, err := client.Do(req)
102+
if err != nil {
103+
return nil, err
104+
}
99105

100-
retval := make([]idp.Group, len(groups.Value))
101-
for i, group := range groups.Value {
102-
retvalMembers := make([]string, len(group.Members))
103-
for j, member := range group.Members {
104-
retvalMembers[j] = member.Id
106+
var groups getGroupsResponse
107+
err = json.NewDecoder(resp.Body).Decode(&groups)
108+
_ = resp.Body.Close()
109+
if err != nil {
110+
return nil, err
105111
}
106112

107-
retval[i] = idp.Group{
108-
ID: group.Id,
109-
Name: group.DisplayName,
110-
Members: retvalMembers,
113+
for _, group := range groups.Value {
114+
retvalMembers := make([]string, len(group.Members))
115+
for j, member := range group.Members {
116+
retvalMembers[j] = member.Id
117+
}
118+
119+
retval = append(retval, idp.Group{
120+
ID: group.Id,
121+
Name: group.DisplayName,
122+
Members: retvalMembers,
123+
})
111124
}
125+
126+
getGroupsURL = groups.NextLink
112127
}
113128

114129
return retval, nil
@@ -144,6 +159,18 @@ func (a *Client) getAccessToken() (string, error) {
144159
return "", errors.New("failed to get access token")
145160
}
146161

162+
func buildPrefixFilter(field string, prefixes []string) string {
163+
if len(prefixes) == 0 {
164+
return ""
165+
}
166+
167+
if len(prefixes) == 1 {
168+
return fmt.Sprintf("$filter=startswith(%s,'%s')", field, prefixes[0])
169+
}
170+
171+
return buildPrefixFilter(field, prefixes[1:]) + fmt.Sprintf("%%20or%%20startswith(%s,'%s')", field, prefixes[0])
172+
}
173+
147174
type getUsersResponse struct {
148175
OdataContext string `json:"@odata.context"`
149176
Value []struct {
@@ -152,6 +179,7 @@ type getUsersResponse struct {
152179
DisplayName string `json:"displayName"`
153180
AccountEnabled bool `json:"accountEnabled"`
154181
} `json:"value"`
182+
NextLink string `json:"@odata.nextLink"`
155183
}
156184

157185
type getGroupsResponse struct {
@@ -164,4 +192,5 @@ type getGroupsResponse struct {
164192
Id string `json:"id"`
165193
} `json:"members"`
166194
} `json:"value"`
195+
NextLink string `json:"@odata.nextLink"`
167196
}

pro/idp/google/google.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"encoding/base64"
66
"encoding/json"
7+
78
"github.com/gravitl/netmaker/logic"
89
"github.com/gravitl/netmaker/pro/idp"
910
admindir "google.golang.org/api/admin/directory/v1"
@@ -59,7 +60,7 @@ func NewGoogleWorkspaceClient() (*Client, error) {
5960
}, nil
6061
}
6162

62-
func (g *Client) GetUsers() ([]idp.User, error) {
63+
func (g *Client) GetUsers(filters []string) ([]idp.User, error) {
6364
var retval []idp.User
6465
err := g.service.Users.List().
6566
Customer("my_customer").
@@ -81,7 +82,7 @@ func (g *Client) GetUsers() ([]idp.User, error) {
8182
return retval, err
8283
}
8384

84-
func (g *Client) GetGroups() ([]idp.Group, error) {
85+
func (g *Client) GetGroups(filters []string) ([]idp.Group, error) {
8586
var retval []idp.Group
8687
err := g.service.Groups.List().
8788
Customer("my_customer").

pro/idp/idp.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package idp
22

33
type Client interface {
4-
GetUsers() ([]User, error)
5-
GetGroups() ([]Group, error)
4+
GetUsers(filters []string) ([]User, error)
5+
GetGroups(filters []string) ([]Group, error)
66
}
77

88
type User struct {

0 commit comments

Comments
 (0)