Resolves #2111 Option to fetch transitive group membership

Signed-off-by: Matt Hoey <matt.hoey@missionlane.com>
This commit is contained in:
Matt Hoey 2021-09-07 20:21:26 -07:00
parent a15cd8788f
commit ee5b5b25bd
1 changed files with 48 additions and 20 deletions

View File

@ -49,6 +49,9 @@ type Config struct {
// The email of a GSuite super user which the service account will impersonate // The email of a GSuite super user which the service account will impersonate
// when listing groups // when listing groups
AdminEmail string AdminEmail string
// If this field is true, fetch direct group membership and transitive group membership
FetchTransitiveGroupMembership bool `json:"fetchTransitiveGroupMembership"`
} }
// Open returns a connector which can be used to login users through Google. // Open returns a connector which can be used to login users through Google.
@ -87,13 +90,14 @@ func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, e
verifier: provider.Verifier( verifier: provider.Verifier(
&oidc.Config{ClientID: clientID}, &oidc.Config{ClientID: clientID},
), ),
logger: logger, logger: logger,
cancel: cancel, cancel: cancel,
hostedDomains: c.HostedDomains, hostedDomains: c.HostedDomains,
groups: c.Groups, groups: c.Groups,
serviceAccountFilePath: c.ServiceAccountFilePath, serviceAccountFilePath: c.ServiceAccountFilePath,
adminEmail: c.AdminEmail, adminEmail: c.AdminEmail,
adminSrv: srv, fetchTransitiveGroupMembership: c.FetchTransitiveGroupMembership,
adminSrv: srv,
}, nil }, nil
} }
@ -103,16 +107,17 @@ var (
) )
type googleConnector struct { type googleConnector struct {
redirectURI string redirectURI string
oauth2Config *oauth2.Config oauth2Config *oauth2.Config
verifier *oidc.IDTokenVerifier verifier *oidc.IDTokenVerifier
cancel context.CancelFunc cancel context.CancelFunc
logger log.Logger logger log.Logger
hostedDomains []string hostedDomains []string
groups []string groups []string
serviceAccountFilePath string serviceAccountFilePath string
adminEmail string adminEmail string
adminSrv *admin.Service fetchTransitiveGroupMembership bool
adminSrv *admin.Service
} }
func (c *googleConnector) Close() error { func (c *googleConnector) Close() error {
@ -214,7 +219,7 @@ func (c *googleConnector) createIdentity(ctx context.Context, identity connector
var groups []string var groups []string
if s.Groups && c.adminSrv != nil { if s.Groups && c.adminSrv != nil {
groups, err = c.getGroups(claims.Email) groups, err = c.getGroups(claims.Email, c.fetchTransitiveGroupMembership)
if err != nil { if err != nil {
return identity, fmt.Errorf("google: could not retrieve groups: %v", err) return identity, fmt.Errorf("google: could not retrieve groups: %v", err)
} }
@ -240,7 +245,7 @@ func (c *googleConnector) createIdentity(ctx context.Context, identity connector
// getGroups creates a connection to the admin directory service and lists // getGroups creates a connection to the admin directory service and lists
// all groups the user is a member of // all groups the user is a member of
func (c *googleConnector) getGroups(email string) ([]string, error) { func (c *googleConnector) getGroups(email string, fetchTransitiveGroupMembership bool) ([]string, error) {
var userGroups []string var userGroups []string
var err error var err error
groupsList := &admin.Groups{} groupsList := &admin.Groups{}
@ -254,6 +259,16 @@ func (c *googleConnector) getGroups(email string) ([]string, error) {
for _, group := range groupsList.Groups { for _, group := range groupsList.Groups {
// TODO (joelspeed): Make desired group key configurable // TODO (joelspeed): Make desired group key configurable
userGroups = append(userGroups, group.Email) userGroups = append(userGroups, group.Email)
// getGroups takes a user's email/alias as well as a group's email/alias
if fetchTransitiveGroupMembership {
transitiveGroups, err := c.getGroups(group.Email, fetchTransitiveGroupMembership)
if err != nil {
return nil, fmt.Errorf("could not list transitive groups: %v", err)
}
userGroups = append(userGroups, transitiveGroups...)
}
} }
if groupsList.NextPageToken == "" { if groupsList.NextPageToken == "" {
@ -261,7 +276,7 @@ func (c *googleConnector) getGroups(email string) ([]string, error) {
} }
} }
return userGroups, nil return uniqueGroups(userGroups), nil
} }
// createDirectoryService loads a google service account credentials file, // createDirectoryService loads a google service account credentials file,
@ -296,3 +311,16 @@ func createDirectoryService(serviceAccountFilePath string, email string) (*admin
} }
return srv, nil return srv, nil
} }
// uniqueGroups returns the unique groups of a slice
func uniqueGroups(groups []string) []string {
keys := make(map[string]struct{})
unique := []string{}
for _, group := range groups {
if _, exists := keys[group]; !exists {
keys[group] = struct{}{}
unique = append(unique, group)
}
}
return unique
}