snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
This commit is contained in:
297
selfservice/strategy/oidc/provider_uaepass_test.go
Normal file
297
selfservice/strategy/oidc/provider_uaepass_test.go
Normal file
@@ -0,0 +1,297 @@
|
||||
// Copyright © 2025 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package oidc_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/ory/kratos/pkg"
|
||||
"github.com/ory/kratos/selfservice/strategy/oidc"
|
||||
)
|
||||
|
||||
func TestUAEPassOIDCClaims(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := http.NewServeMux()
|
||||
expectedAccessToken := "my-access-token"
|
||||
|
||||
handler.HandleFunc("GET /userinfo", func(w http.ResponseWriter, r *http.Request) {
|
||||
// Verify Bearer token authentication
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if authHeader != "Bearer "+expectedAccessToken {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
_, _ = w.Write([]byte(`{"error": "invalid_token"}`))
|
||||
return
|
||||
}
|
||||
|
||||
// UAE PASS citizen/resident profile response
|
||||
userProfile := `{
|
||||
"sub": "uaepass-user-123456",
|
||||
"email": "user@example.ae",
|
||||
"fullnameEN": "Ahmed Mohammed Al Rashid",
|
||||
"fullnameAR": "أحمد محمد الراشد",
|
||||
"firstnameEN": "Ahmed",
|
||||
"firstnameAR": "أحمد",
|
||||
"lastnameEN": "Al Rashid",
|
||||
"lastnameAR": "الراشد",
|
||||
"uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"unifiedId": "784-1990-1234567-1",
|
||||
"idn": "784199012345671",
|
||||
"userType": "SOP1",
|
||||
"nationalityEN": "ARE",
|
||||
"gender": "M",
|
||||
"dob": "1990-05-15",
|
||||
"mobile": "+971501234567"
|
||||
}`
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err := w.Write([]byte(userProfile))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
uaePassAPI := httptest.NewServer(handler)
|
||||
t.Cleanup(uaePassAPI.Close)
|
||||
|
||||
_, reg := pkg.NewFastRegistryWithMocks(t)
|
||||
p := oidc.NewProviderUAEPass(&oidc.Configuration{
|
||||
Scope: []string{"urn:uae:digitalid:profile:general"},
|
||||
}, reg).(*oidc.ProviderUAEPass)
|
||||
p.SetUserInfoURL(t, uaePassAPI.URL+"/userinfo")
|
||||
|
||||
claims, err := p.Claims(t.Context(), &oauth2.Token{AccessToken: expectedAccessToken}, nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, claims)
|
||||
|
||||
// Verify standard claims
|
||||
assert.Equal(t, "uaepass-user-123456", claims.Subject)
|
||||
assert.Equal(t, "user@example.ae", claims.Email)
|
||||
assert.Equal(t, "Ahmed Mohammed Al Rashid", claims.Name)
|
||||
assert.Equal(t, "Ahmed", claims.GivenName)
|
||||
assert.Equal(t, "Al Rashid", claims.LastName)
|
||||
|
||||
// Verify raw claims contain UAE PASS specific data
|
||||
require.NotNil(t, claims.RawClaims)
|
||||
assert.Equal(t, "550e8400-e29b-41d4-a716-446655440000", claims.RawClaims["uuid"])
|
||||
assert.Equal(t, "784-1990-1234567-1", claims.RawClaims["unifiedId"])
|
||||
assert.Equal(t, "784199012345671", claims.RawClaims["idn"])
|
||||
assert.Equal(t, "SOP1", claims.RawClaims["userType"])
|
||||
assert.Equal(t, "ARE", claims.RawClaims["nationalityEN"])
|
||||
assert.Equal(t, "M", claims.RawClaims["gender"])
|
||||
assert.Equal(t, "1990-05-15", claims.RawClaims["dob"])
|
||||
assert.Equal(t, "+971501234567", claims.RawClaims["mobile"])
|
||||
assert.Equal(t, "أحمد محمد الراشد", claims.RawClaims["fullnameAR"])
|
||||
}
|
||||
|
||||
func TestUAEPassOIDCClaimsVisitor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := http.NewServeMux()
|
||||
expectedAccessToken := "visitor-access-token"
|
||||
|
||||
handler.HandleFunc("GET /userinfo", func(w http.ResponseWriter, r *http.Request) {
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if authHeader != "Bearer "+expectedAccessToken {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// UAE PASS visitor profile response (limited data)
|
||||
userProfile := `{
|
||||
"sub": "uaepass-visitor-789",
|
||||
"email": "visitor@example.com",
|
||||
"fullnameEN": "John Smith",
|
||||
"firstnameEN": "John",
|
||||
"lastnameEN": "Smith",
|
||||
"uuid": "660e8400-e29b-41d4-a716-446655440001",
|
||||
"userType": "SOP3"
|
||||
}`
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(userProfile))
|
||||
})
|
||||
|
||||
uaePassAPI := httptest.NewServer(handler)
|
||||
t.Cleanup(uaePassAPI.Close)
|
||||
|
||||
_, reg := pkg.NewFastRegistryWithMocks(t)
|
||||
p := oidc.NewProviderUAEPass(&oidc.Configuration{
|
||||
Scope: []string{"urn:uae:digitalid:profile"},
|
||||
}, reg).(*oidc.ProviderUAEPass)
|
||||
p.SetUserInfoURL(t, uaePassAPI.URL+"/userinfo")
|
||||
|
||||
claims, err := p.Claims(t.Context(), &oauth2.Token{AccessToken: expectedAccessToken}, nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, claims)
|
||||
|
||||
assert.Equal(t, "uaepass-visitor-789", claims.Subject)
|
||||
assert.Equal(t, "visitor@example.com", claims.Email)
|
||||
assert.Equal(t, "John Smith", claims.Name)
|
||||
assert.Equal(t, "SOP3", claims.RawClaims["userType"])
|
||||
}
|
||||
|
||||
func TestUAEPassOIDCClaimsError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := http.NewServeMux()
|
||||
|
||||
handler.HandleFunc("GET /userinfo", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
_, _ = w.Write([]byte(`{"error": "invalid_token", "error_description": "The access token is expired"}`))
|
||||
})
|
||||
|
||||
uaePassAPI := httptest.NewServer(handler)
|
||||
t.Cleanup(uaePassAPI.Close)
|
||||
|
||||
_, reg := pkg.NewFastRegistryWithMocks(t)
|
||||
p := oidc.NewProviderUAEPass(&oidc.Configuration{
|
||||
Scope: []string{"urn:uae:digitalid:profile:general"},
|
||||
}, reg).(*oidc.ProviderUAEPass)
|
||||
p.SetUserInfoURL(t, uaePassAPI.URL+"/userinfo")
|
||||
|
||||
claims, err := p.Claims(t.Context(), &oauth2.Token{AccessToken: "invalid-token"}, nil)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, claims)
|
||||
}
|
||||
|
||||
func TestUAEPassIssuerURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, reg := pkg.NewFastRegistryWithMocks(t)
|
||||
|
||||
// Test staging via IssuerURL
|
||||
pStaging := oidc.NewProviderUAEPass(&oidc.Configuration{
|
||||
IssuerURL: "https://stg-id.uaepass.ae",
|
||||
Scope: []string{"urn:uae:digitalid:profile:general"},
|
||||
}, reg).(*oidc.ProviderUAEPass)
|
||||
|
||||
oauth2Config, err := pStaging.OAuth2(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "https://stg-id.uaepass.ae/idshub/authorize", oauth2Config.Endpoint.AuthURL)
|
||||
assert.Equal(t, "https://stg-id.uaepass.ae/idshub/token", oauth2Config.Endpoint.TokenURL)
|
||||
|
||||
// Test production via IssuerURL
|
||||
pProduction := oidc.NewProviderUAEPass(&oidc.Configuration{
|
||||
IssuerURL: "https://id.uaepass.ae",
|
||||
Scope: []string{"urn:uae:digitalid:profile:general"},
|
||||
}, reg).(*oidc.ProviderUAEPass)
|
||||
|
||||
oauth2Config, err = pProduction.OAuth2(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "https://id.uaepass.ae/idshub/authorize", oauth2Config.Endpoint.AuthURL)
|
||||
assert.Equal(t, "https://id.uaepass.ae/idshub/token", oauth2Config.Endpoint.TokenURL)
|
||||
|
||||
// Test default (production) when IssuerURL is not set
|
||||
pDefault := oidc.NewProviderUAEPass(&oidc.Configuration{
|
||||
Scope: []string{"urn:uae:digitalid:profile:general"},
|
||||
}, reg).(*oidc.ProviderUAEPass)
|
||||
|
||||
oauth2Config, err = pDefault.OAuth2(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "https://id.uaepass.ae/idshub/authorize", oauth2Config.Endpoint.AuthURL)
|
||||
assert.Equal(t, "https://id.uaepass.ae/idshub/token", oauth2Config.Endpoint.TokenURL)
|
||||
|
||||
// Test trailing slash is handled correctly
|
||||
pTrailingSlash := oidc.NewProviderUAEPass(&oidc.Configuration{
|
||||
IssuerURL: "https://stg-id.uaepass.ae/",
|
||||
Scope: []string{"urn:uae:digitalid:profile:general"},
|
||||
}, reg).(*oidc.ProviderUAEPass)
|
||||
|
||||
oauth2Config, err = pTrailingSlash.OAuth2(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "https://stg-id.uaepass.ae/idshub/authorize", oauth2Config.Endpoint.AuthURL)
|
||||
assert.Equal(t, "https://stg-id.uaepass.ae/idshub/token", oauth2Config.Endpoint.TokenURL)
|
||||
}
|
||||
|
||||
func TestUAEPassConfigurationValidation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, reg := pkg.NewFastRegistryWithMocks(t)
|
||||
|
||||
// Test PKCE auto falls back to force (no error)
|
||||
pPKCEAuto := oidc.NewProviderUAEPass(&oidc.Configuration{
|
||||
PKCE: "auto",
|
||||
Scope: []string{"urn:uae:digitalid:profile:general"},
|
||||
}, reg).(*oidc.ProviderUAEPass)
|
||||
|
||||
_, err := pPKCEAuto.OAuth2(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "force", pPKCEAuto.Config().PKCE)
|
||||
|
||||
// Test PKCE unset also falls back to force
|
||||
pPKCEUnset := oidc.NewProviderUAEPass(&oidc.Configuration{
|
||||
Scope: []string{"urn:uae:digitalid:profile:general"},
|
||||
}, reg).(*oidc.ProviderUAEPass)
|
||||
|
||||
_, err = pPKCEUnset.OAuth2(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "force", pPKCEUnset.Config().PKCE)
|
||||
|
||||
// Test valid configuration
|
||||
pValid := oidc.NewProviderUAEPass(&oidc.Configuration{
|
||||
PKCE: "never",
|
||||
Scope: []string{"urn:uae:digitalid:profile:general"},
|
||||
}, reg).(*oidc.ProviderUAEPass)
|
||||
|
||||
oauth2Config, err := pValid.OAuth2(t.Context())
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, oauth2Config)
|
||||
}
|
||||
|
||||
func TestUAEPassAuthFlowDefaultsToWeb(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, reg := pkg.NewFastRegistryWithMocks(t)
|
||||
|
||||
p := oidc.NewProviderUAEPass(&oidc.Configuration{
|
||||
Scope: []string{"urn:uae:digitalid:profile:general"},
|
||||
}, reg).(*oidc.ProviderUAEPass)
|
||||
|
||||
options := p.AuthCodeURLOptions(nil)
|
||||
require.Len(t, options, 1)
|
||||
|
||||
oauth2Config, err := p.OAuth2(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
authURL := oauth2Config.AuthCodeURL("test-state", options...)
|
||||
parsedURL, err := url.Parse(authURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
acrValue := parsedURL.Query().Get("acr_values")
|
||||
assert.Equal(t, oidc.UAEPassACRWeb, acrValue)
|
||||
}
|
||||
|
||||
func TestUAEPassMobileACRViaUpstreamParams(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, reg := pkg.NewFastRegistryWithMocks(t)
|
||||
|
||||
p := oidc.NewProviderUAEPass(&oidc.Configuration{
|
||||
Scope: []string{"urn:uae:digitalid:profile:general"},
|
||||
}, reg).(*oidc.ProviderUAEPass)
|
||||
|
||||
oauth2Config, err := p.OAuth2(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Simulate the ordering in getAuthRedirectURL: provider defaults first, then upstream params.
|
||||
// Upstream acr_values should override the provider's default web ACR value.
|
||||
var opts []oauth2.AuthCodeOption
|
||||
opts = append(opts, p.AuthCodeURLOptions(nil)...)
|
||||
opts = append(opts, oidc.UpstreamParameters(map[string]string{
|
||||
"acr_values": "urn:digitalid:authentication:flow:mobileondevice",
|
||||
})...)
|
||||
|
||||
authURL := oauth2Config.AuthCodeURL("test-state", opts...)
|
||||
parsedURL, err := url.Parse(authURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
acrValue := parsedURL.Query().Get("acr_values")
|
||||
assert.Equal(t, "urn:digitalid:authentication:flow:mobileondevice", acrValue)
|
||||
}
|
||||
Reference in New Issue
Block a user