Skip to content

Commit

Permalink
microsoft: option for group UUIDs instead of name and group whitelist
Browse files Browse the repository at this point in the history
  • Loading branch information
maksd committed Jul 25, 2019
1 parent 20a858d commit 4585850
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 29 deletions.
6 changes: 6 additions & 0 deletions Documentation/connectors/microsoft.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
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
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
Expand All @@ -110,3 +113,6 @@ connectors:
- developers
- devops
```

Also, `useGroupsAsWhitelist` configuration option, can restrict the groups
claims to include only the user's groups that are in the configured `groups`.
88 changes: 59 additions & 29 deletions connector/microsoft/microsoft.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,42 +19,66 @@ import (
"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 (
apiURL = "https://graph.microsoft.com"
// Microsoft requires this scope to access user's profile
scopeUser = "user.read"
// 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"
)

// Config holds configuration options for microsoft logins.
type Config struct {
ClientID string `json:"clientID"`
ClientSecret string `json:"clientSecret"`
RedirectURI string `json:"redirectURI"`
Tenant string `json:"tenant"`
OnlySecurityGroups bool `json:"onlySecurityGroups"`
Groups []string `json:"groups"`
ClientID string `json:"clientID"`
ClientSecret string `json:"clientSecret"`
RedirectURI string `json:"redirectURI"`
Tenant string `json:"tenant"`
OnlySecurityGroups bool `json:"onlySecurityGroups"`
Groups []string `json:"groups"`
GroupNameFormat GroupNameFormat `json:"groupNameFormat"`
UseGroupsAsWhitelist bool `json:"useGroupsAsWhitelist"`
}

// Open returns a strategy for logging in through Microsoft.
func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) {
m := microsoftConnector{
redirectURI: c.RedirectURI,
clientID: c.ClientID,
clientSecret: c.ClientSecret,
tenant: c.Tenant,
onlySecurityGroups: c.OnlySecurityGroups,
groups: c.Groups,
logger: logger,
redirectURI: c.RedirectURI,
clientID: c.ClientID,
clientSecret: c.ClientSecret,
tenant: c.Tenant,
onlySecurityGroups: c.OnlySecurityGroups,
groups: c.Groups,
groupNameFormat: c.GroupNameFormat,
useGroupsAsWhitelist: c.UseGroupsAsWhitelist,
logger: logger,
}
// By default allow logins from both personal and business/school
// accounts.
if m.tenant == "" {
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
}

Expand All @@ -70,13 +94,15 @@ var (
)

type microsoftConnector struct {
redirectURI string
clientID string
clientSecret string
tenant string
onlySecurityGroups bool
groups []string
logger log.Logger
redirectURI string
clientID string
clientSecret string
tenant string
onlySecurityGroups bool
groupNameFormat GroupNameFormat
groups []string
useGroupsAsWhitelist bool
logger log.Logger
}

func (c *microsoftConnector) isOrgTenant() bool {
Expand Down Expand Up @@ -300,24 +326,28 @@ type group struct {
Name string `json:"displayName"`
}

func (c *microsoftConnector) getGroups(ctx context.Context, client *http.Client, userID string) (groups []string, err error) {
ids, err := c.getGroupIDs(ctx, client)
func (c *microsoftConnector) getGroups(ctx context.Context, client *http.Client, userID string) ([]string, error) {
userGroups, err := c.getGroupIDs(ctx, client)
if err != nil {
return groups, err
return nil, err
}

groups, err = c.getGroupNames(ctx, client, ids)
if err != nil {
return
if c.groupNameFormat == GroupName {
userGroups, err = c.getGroupNames(ctx, client, userGroups)
if err != nil {
return nil, err
}
}

// 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 {
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) {
Expand Down

0 comments on commit 4585850

Please sign in to comment.