Browse Source

Merge 2cd0cd54ff into 13f012fb81

pull/2902/merge
xaurx 4 days ago committed by GitHub
parent
commit
def2bb16b2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 115
      connector/microsoft/microsoft.go

115
connector/microsoft/microsoft.go

@ -16,6 +16,7 @@ import (
"golang.org/x/oauth2"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/dexidp/dex/connector"
groups_pkg "github.com/dexidp/dex/pkg/groups"
)
@ -32,8 +33,6 @@ const (
)
const (
// 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 ids to groups names.
scopeGroups = "directory.read.all"
@ -62,7 +61,7 @@ type Config struct {
PromptType string `json:"promptType"`
DomainHint string `json:"domainHint"`
Scopes []string `json:"scopes"` // defaults to scopeUser (user.read)
Scopes []string `json:"scopes"` // defaults to openid,profile,email
}
// Open returns a strategy for logging in through Microsoft.
@ -98,6 +97,7 @@ func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, erro
if m.tenant == "" {
m.tenant = "common"
}
m.issuer = m.apiURL + "/{tenantid}/v2.0"
// By default, use group names
switch m.groupNameFormat {
@ -108,6 +108,24 @@ func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, erro
return nil, fmt.Errorf("invalid groupNameFormat: %s", m.groupNameFormat)
}
issuer := strings.ReplaceAll(m.issuer, "{tenantid}", m.tenant)
ctx := oidc.InsecureIssuerURLContext(context.Background(), issuer)
provider, err := oidc.NewProvider(ctx, issuer)
if err != nil {
return nil, fmt.Errorf("provider error: %v", err)
}
var pclaims map[string]interface{}
if err := provider.Claims(&pclaims); err != nil {
return nil, fmt.Errorf("oidc: failed to decode provider claims: %v", err)
}
if pissuer, found := pclaims["issuer"]; !found || pissuer != m.issuer {
return nil, fmt.Errorf("oidc: incorrect prodiver issuer in well known configuration %q", pissuer)
}
m.provider = provider
return &m, nil
}
@ -125,6 +143,8 @@ var (
type microsoftConnector struct {
apiURL string
graphURL string
issuer string // issuer for discovery of well known configuration
provider *oidc.Provider
redirectURI string
clientID string
clientSecret string
@ -153,7 +173,7 @@ func (c *microsoftConnector) oauth2Config(scopes connector.Scopes) *oauth2.Confi
if len(c.scopes) > 0 {
microsoftScopes = c.scopes
} else {
microsoftScopes = append(microsoftScopes, scopeUser)
microsoftScopes = append(microsoftScopes, oidc.ScopeOpenID, "profile", "email")
}
if c.groupsRequired(scopes.Groups) {
microsoftScopes = append(microsoftScopes, scopeGroups)
@ -208,7 +228,7 @@ func (c *microsoftConnector) HandleCallback(s connector.Scopes, connData []byte,
client := oauth2Config.Client(ctx, token)
user, err := c.user(ctx, client)
user, err := c.userFromToken(ctx, token)
if err != nil {
return identity, fmt.Errorf("microsoft: get user: %v", err)
}
@ -307,7 +327,7 @@ func (c *microsoftConnector) Refresh(ctx context.Context, s connector.Scopes, id
return nil
},
})
user, err := c.user(ctx, client)
user, err := c.userFromToken(ctx, tok)
if err != nil {
return identity, fmt.Errorf("microsoft: get user: %v", err)
}
@ -326,57 +346,68 @@ func (c *microsoftConnector) Refresh(ctx context.Context, s connector.Scopes, id
return identity, nil
}
// https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/resources/user
// id - The unique identifier for the user. Inherited from
//
// directoryObject. Key. Not nullable. Read-only.
//
// displayName - The name displayed in the address book for the user.
//
// This is usually the combination of the user's first name,
// middle initial and last name. This property is required
// when a user is created and it cannot be cleared during
// updates. Supports $filter and $orderby.
//
// userPrincipalName - The user principal name (UPN) of the user.
//
// The UPN is an Internet-style login name for the user
// based on the Internet standard RFC 822. By convention,
// this should map to the user's email name. The general
// format is alias@domain, where domain must be present in
// the tenant’s collection of verified domains. This
// property is required when a user is created. The
// verified domains for the tenant can be accessed from the
// verifiedDomains property of organization. Supports
// $filter and $orderby.
type user struct {
ID string `json:"id"`
Name string `json:"displayName"`
Email string `json:"userPrincipalName"`
}
func (c *microsoftConnector) user(ctx context.Context, client *http.Client) (u user, err error) {
// https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_get
req, err := http.NewRequest("GET", c.graphURL+"/v1.0/me?$select=id,displayName,userPrincipalName", nil)
if err != nil {
return u, fmt.Errorf("new req: %v", err)
func (c *microsoftConnector) userFromToken(ctx context.Context, token *oauth2.Token) (u user, err error) {
rawIDToken, ok := token.Extra("id_token").(string)
if !ok {
return u, errors.New("oidc: no id_token in token response")
}
resp, err := client.Do(req.WithContext(ctx))
// NOTE: issuer is verified below manually
verifier := c.provider.Verifier(
&oidc.Config{ClientID: c.clientID, SkipIssuerCheck: true},
)
idToken, err := verifier.Verify(ctx, rawIDToken)
if err != nil {
return u, fmt.Errorf("get URL %v", err)
return u, fmt.Errorf("oidc: failed to verify ID Token: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return u, newGraphError(resp.Body)
var claims map[string]interface{}
if err := idToken.Claims(&claims); err != nil {
return u, fmt.Errorf("oidc: failed to decode claims: %v", err)
}
// https://docs.microsoft.com/en-us/azure/active-directory/develop/id-tokens
tid, found := claims["tid"].(string)
if !found {
return u, errors.New("missing 'tid' claim")
}
iss, found := claims["iss"].(string)
if !found {
return u, errors.New("missing 'iss' claim")
}
objectID, found := claims["oid"].(string)
if !found {
return u, errors.New("missing 'oid' claim")
}
name, found := claims["name"].(string)
if !found {
return u, errors.New("missing 'name' claim")
}
email, found := claims["email"].(string)
if !found {
return u, errors.New("missing 'email' claim")
}
if err := json.NewDecoder(resp.Body).Decode(&u); err != nil {
return u, fmt.Errorf("JSON decode: %v", err)
if iss != strings.ReplaceAll(c.issuer, "{tenantid}", tid) {
return u, fmt.Errorf("incorrect token issuer:", iss)
}
return u, err
return user{
ID: objectID,
Name: name,
Email: email,
}, nil
}
// https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/resources/group

Loading…
Cancel
Save