microsoft: option for group UUIDs instead of name and group whitelist
This commit is contained in:
		| @@ -88,6 +88,9 @@ a member of. `onlySecurityGroups` configuration option restricts the list to | |||||||
| include only security groups. By default all groups (security, Office 365, | include only security groups. By default all groups (security, Office 365, | ||||||
| mailing lists) are included. | mailing lists) are included. | ||||||
|  |  | ||||||
|  | By default, dex resolve groups ids to groups names, to keep groups ids, you can | ||||||
|  | specify the configuration option `groupNameFormat: id`. | ||||||
|  |  | ||||||
| It is possible to require a user to be a member of a particular group in order | It is possible to require a user to be a member of a particular group in order | ||||||
| to be successfully authenticated in dex. For example, with the following | to be successfully authenticated in dex. For example, with the following | ||||||
| configuration file only the users who are members of at least one of the listed | configuration file only the users who are members of at least one of the listed | ||||||
| @@ -110,3 +113,6 @@ connectors: | |||||||
|         - developers |         - developers | ||||||
|         - devops |         - devops | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | Also, `useGroupsAsWhitelist` configuration option, can restrict the groups | ||||||
|  | claims to include only the user's groups that are in the configured `groups`. | ||||||
| @@ -19,35 +19,50 @@ import ( | |||||||
| 	"github.com/dexidp/dex/pkg/log" | 	"github.com/dexidp/dex/pkg/log" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | // GroupNameFormat represents the format of the group identifier | ||||||
|  | // we use type of string instead of int because it's easier to | ||||||
|  | // marshall/unmarshall | ||||||
|  | type GroupNameFormat string | ||||||
|  |  | ||||||
|  | // Possible values for GroupNameFormat | ||||||
|  | const ( | ||||||
|  | 	GroupID   GroupNameFormat = "id" | ||||||
|  | 	GroupName GroupNameFormat = "name" | ||||||
|  | ) | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| 	apiURL = "https://graph.microsoft.com" | 	apiURL = "https://graph.microsoft.com" | ||||||
| 	// Microsoft requires this scope to access user's profile | 	// Microsoft requires this scope to access user's profile | ||||||
| 	scopeUser = "user.read" | 	scopeUser = "user.read" | ||||||
| 	// Microsoft requires this scope to list groups the user is a member of | 	// Microsoft requires this scope to list groups the user is a member of | ||||||
| 	// and resolve their UUIDs to groups names. | 	// and resolve their ids to groups names. | ||||||
| 	scopeGroups = "directory.read.all" | 	scopeGroups = "directory.read.all" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Config holds configuration options for microsoft logins. | // Config holds configuration options for microsoft logins. | ||||||
| type Config struct { | type Config struct { | ||||||
| 	ClientID           string   `json:"clientID"` | 	ClientID             string          `json:"clientID"` | ||||||
| 	ClientSecret       string   `json:"clientSecret"` | 	ClientSecret         string          `json:"clientSecret"` | ||||||
| 	RedirectURI        string   `json:"redirectURI"` | 	RedirectURI          string          `json:"redirectURI"` | ||||||
| 	Tenant             string   `json:"tenant"` | 	Tenant               string          `json:"tenant"` | ||||||
| 	OnlySecurityGroups bool     `json:"onlySecurityGroups"` | 	OnlySecurityGroups   bool            `json:"onlySecurityGroups"` | ||||||
| 	Groups             []string `json:"groups"` | 	Groups               []string        `json:"groups"` | ||||||
|  | 	GroupNameFormat      GroupNameFormat `json:"groupNameFormat"` | ||||||
|  | 	UseGroupsAsWhitelist bool            `json:"useGroupsAsWhitelist"` | ||||||
| } | } | ||||||
|  |  | ||||||
| // Open returns a strategy for logging in through Microsoft. | // Open returns a strategy for logging in through Microsoft. | ||||||
| func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { | func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { | ||||||
| 	m := microsoftConnector{ | 	m := microsoftConnector{ | ||||||
| 		redirectURI:        c.RedirectURI, | 		redirectURI:          c.RedirectURI, | ||||||
| 		clientID:           c.ClientID, | 		clientID:             c.ClientID, | ||||||
| 		clientSecret:       c.ClientSecret, | 		clientSecret:         c.ClientSecret, | ||||||
| 		tenant:             c.Tenant, | 		tenant:               c.Tenant, | ||||||
| 		onlySecurityGroups: c.OnlySecurityGroups, | 		onlySecurityGroups:   c.OnlySecurityGroups, | ||||||
| 		groups:             c.Groups, | 		groups:               c.Groups, | ||||||
| 		logger:             logger, | 		groupNameFormat:      c.GroupNameFormat, | ||||||
|  | 		useGroupsAsWhitelist: c.UseGroupsAsWhitelist, | ||||||
|  | 		logger:               logger, | ||||||
| 	} | 	} | ||||||
| 	// By default allow logins from both personal and business/school | 	// By default allow logins from both personal and business/school | ||||||
| 	// accounts. | 	// accounts. | ||||||
| @@ -55,6 +70,15 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) | |||||||
| 		m.tenant = "common" | 		m.tenant = "common" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// By default, use group names | ||||||
|  | 	switch m.groupNameFormat { | ||||||
|  | 	case "": | ||||||
|  | 		m.groupNameFormat = GroupName | ||||||
|  | 	case GroupID, GroupName: | ||||||
|  | 	default: | ||||||
|  | 		return nil, fmt.Errorf("invalid groupNameFormat: %s", m.groupNameFormat) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return &m, nil | 	return &m, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -70,13 +94,15 @@ var ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| type microsoftConnector struct { | type microsoftConnector struct { | ||||||
| 	redirectURI        string | 	redirectURI          string | ||||||
| 	clientID           string | 	clientID             string | ||||||
| 	clientSecret       string | 	clientSecret         string | ||||||
| 	tenant             string | 	tenant               string | ||||||
| 	onlySecurityGroups bool | 	onlySecurityGroups   bool | ||||||
| 	groups             []string | 	groupNameFormat      GroupNameFormat | ||||||
| 	logger             log.Logger | 	groups               []string | ||||||
|  | 	useGroupsAsWhitelist bool | ||||||
|  | 	logger               log.Logger | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *microsoftConnector) isOrgTenant() bool { | func (c *microsoftConnector) isOrgTenant() bool { | ||||||
| @@ -300,24 +326,28 @@ type group struct { | |||||||
| 	Name string `json:"displayName"` | 	Name string `json:"displayName"` | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *microsoftConnector) getGroups(ctx context.Context, client *http.Client, userID string) (groups []string, err error) { | func (c *microsoftConnector) getGroups(ctx context.Context, client *http.Client, userID string) ([]string, error) { | ||||||
| 	ids, err := c.getGroupIDs(ctx, client) | 	userGroups, err := c.getGroupIDs(ctx, client) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return groups, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	groups, err = c.getGroupNames(ctx, client, ids) | 	if c.groupNameFormat == GroupName { | ||||||
| 	if err != nil { | 		userGroups, err = c.getGroupNames(ctx, client, userGroups) | ||||||
| 		return | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// ensure that the user is in at least one required group | 	// ensure that the user is in at least one required group | ||||||
| 	filteredGroups := groups_pkg.Filter(groups, c.groups) | 	filteredGroups := groups_pkg.Filter(userGroups, c.groups) | ||||||
| 	if len(c.groups) > 0 && len(filteredGroups) == 0 { | 	if len(c.groups) > 0 && len(filteredGroups) == 0 { | ||||||
| 		return nil, fmt.Errorf("microsoft: user %v not in any of the required groups", userID) | 		return nil, fmt.Errorf("microsoft: user %v not in any of the required groups", userID) | ||||||
|  | 	} else if c.useGroupsAsWhitelist { | ||||||
|  | 		return filteredGroups, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return | 	return userGroups, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *microsoftConnector) getGroupIDs(ctx context.Context, client *http.Client) (ids []string, err error) { | func (c *microsoftConnector) getGroupIDs(ctx context.Context, client *http.Client) (ids []string, err error) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user