@@ -0,0 +1,185 @@
13+ "github.com/coreos/go-oidc/v3/oidc"
14+ "github.com/golang-jwt/jwt/v5"
15+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
16+ "github.com/stretchr/testify/assert"
18+ . "github.com/onsi/gomega"
21+func TestAzureEntraOIDCProviderNewMultiTenant(t *testing.T) {
24+ provider := NewMicrosoftEntraIDProvider(&ProviderData{},
25+ options.Provider{OIDCConfig: options.OIDCOptions{
26+ IssuerURL: "https://login.microsoftonline.com/common/v2.0",
27+ InsecureSkipIssuerVerification: true,
30+ g.Expect(provider.Data().ProviderName).To(Equal("Microsoft Entra ID"))
33+func TestAzureEntraOIDCProviderNewSingleTenant(t *testing.T) {
36+ provider := NewMicrosoftEntraIDProvider(&ProviderData{},
37+ options.Provider{OIDCConfig: options.OIDCOptions{
38+ IssuerURL: "https://login.microsoftonline.com/18014347-dd57-41a1-8191-7a1f734ea457/v2.0",
41+ g.Expect(provider.Data().ProviderName).To(Equal("Microsoft Entra ID"))
44+func TestAzureEntraOIDCProviderEnrichSessionGroupOverage(t *testing.T) {
45+ // Create ID Token that indicates group overage with _claim_names
46+ key, _ := rsa.GenerateKey(rand.Reader, 2048)
47+ claimsWithGroupOverage := &claimsWithGroupOverage{
48+ jwt.RegisteredClaims{
49+ Issuer: "https://login.microsoftonline.com/18014347-dd57-41a1-8191-7a1f734ea457/v2.0",
51+ map[string]string{"groups": "src1"},
54+ jwtWithClaims := jwt.NewWithClaims(jwt.SigningMethodRS256, claimsWithGroupOverage)
55+ signedJWT, err := jwtWithClaims.SignedString(key)
57+ assert.NoError(t, err)
59+ session := CreateAuthorizedSession()
60+ session.IDToken = signedJWT
61+ session.Email = "mock@example.com"
64+ provider := NewMicrosoftEntraIDProvider(&ProviderData{},
65+ options.Provider{OIDCConfig: options.OIDCOptions{
66+ IssuerURL: "https://login.microsoftonline.com/18014347-dd57-41a1-8191-7a1f734ea457/v2.0",
70+ // Create mocked Azure Graph server and override Graph URL
71+ mockedGraph := mockGraphAPI(false)
72+ mockedGraphURL, _ := url.Parse(mockedGraph.URL)
73+ updateURL(provider.microsoftGraphURL, mockedGraphURL.Host)
75+ // Test EnrichSession
76+ err = provider.EnrichSession(context.Background(), session)
78+ assert.NoError(t, err)
79+ assert.Contains(t, session.Groups, "85d7d600-7804-4d92-8d43-9c33c21c130c")
80+ assert.Contains(t, session.Groups, "916f0604-8a3b-4a69-bda9-06db11a8f0cd")
81+ assert.Contains(t, session.Groups, "b1aef995-6b55-4ac6-bbfe-e829810e9352", "Pagination using $skiptoken failed")
84+func TestAzureEntraOIDCProviderValidateSessionAllowedTenants(t *testing.T) {
85+ // Create multi-tenant Azure Entra provider with allowed tenants
86+ provider := NewMicrosoftEntraIDProvider(
88+ Verifier: &mockedVerifier{},
91+ OIDCConfig: options.OIDCOptions{
92+ IssuerURL: "https://login.microsoftonline.com/common/v2.0",
93+ InsecureSkipIssuerVerification: true,
94+ InsecureSkipNonce: true,
96+ MicrosoftEntraIDConfig: options.MicrosoftEntraIDOptions{
97+ AllowedTenants: []string{"85d7d600-7804-4d92-8d43-9c33c21c130c"},
102+ // Check for invalid tenant
103+ key, _ := rsa.GenerateKey(rand.Reader, 2048)
105+ idToken := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.RegisteredClaims{
106+ Issuer: "https://login.microsoftonline.com/invalid_tenant/v2.0",
108+ invalidJWT, err := idToken.SignedString(key)
109+ assert.NoError(t, err)
111+ session := CreateAuthorizedSession()
112+ session.IDToken = invalidJWT
114+ valid := provider.ValidateSession(context.Background(), session)
115+ assert.False(t, valid)
117+ // Check for valid tenant
118+ idToken = jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.RegisteredClaims{
119+ Issuer: "https://login.microsoftonline.com/85d7d600-7804-4d92-8d43-9c33c21c130c/v2.0",
121+ validJWT, err := idToken.SignedString(key)
122+ assert.NoError(t, err)
124+ session = CreateAuthorizedSession()
125+ session.IDToken = validJWT
127+ valid = provider.ValidateSession(context.Background(), session)
128+ assert.True(t, valid)
131+func mockGraphAPI(noGroupMemberPermissions bool) *httptest.Server {
132+ groupsPath := "/v1.0/me/transitiveMemberOf"
134+ return httptest.NewServer(http.HandlerFunc(
135+ func(w http.ResponseWriter, r *http.Request) {
136+ if noGroupMemberPermissions {
138+ } else if r.URL.Path == groupsPath && r.Method == http.MethodGet && len(r.URL.Query()["$skiptoken"]) > 0 {
139+ // Second page (pagination)
141+ "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#directoryObjects(id)",
144+ "@odata.type": "#microsoft.graph.group",
145+ "id": "b1aef995-6b55-4ac6-bbfe-e829810e9352"
150+ } else if r.URL.Path == groupsPath && r.Method == http.MethodGet {
151+ // First page (pagination)
152+ w.Write([]byte(fmt.Sprintf(`{
153+ "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#directoryObjects(id)",
154+ "@odata.nextLink": "http://%s/v1.0/me/transitiveMemberOf?$select=id&$top=2&$skiptoken=TEST_TOKEN",
157+ "@odata.type": "#microsoft.graph.group",
158+ "id": "85d7d600-7804-4d92-8d43-9c33c21c130c"
161+ "@odata.type": "#microsoft.graph.group",
162+ "id": "916f0604-8a3b-4a69-bda9-06db11a8f0cd"
171+type claimsWithGroupOverage struct {
172+ jwt.RegisteredClaims
173+ ClaimNames interface{} `json:"_claim_names,omitempty"`
176+func (c *claimsWithGroupOverage) Valid() error {
180+type mockedVerifier struct {
183+func (v *mockedVerifier) Verify(ctx context.Context, rawIDToken string) (*oidc.IDToken, error) {