Merge pull request #1446 from maksd/microsoft-groups-uuid-whitelist
microsoft: option for group UUIDs instead of name and group whitelist
This commit is contained in:
commit
6e98c04f9b
2 changed files with 65 additions and 29 deletions
|
@ -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 a new issue