You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cloudreve/pkg/auth/oidc_consumer_test.go

104 lines
3.4 KiB

package auth
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"testing"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/stretchr/testify/require"
)
func testRSAKeyPEM(t *testing.T) string {
key, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
return string(pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}))
}
func signConsumerToken(t *testing.T, pemKey string, claims *OIDCIDTokenClaims) string {
token, err := SignOIDCIDToken(pemKey, claims)
require.NoError(t, err)
return token
}
func validClaims() *OIDCIDTokenClaims {
return &OIDCIDTokenClaims{
RegisteredClaims: jwt.RegisteredClaims{
Issuer: "https://idp.example.com",
Audience: jwt.ClaimStrings{"cloudreve"},
Subject: "user-1",
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
Nonce: "nonce-1",
Email: "user@example.com",
}
}
func TestVerifyOIDCIDToken(t *testing.T) {
pemKey := testRSAKeyPEM(t)
jwks, err := OIDCJWKSet(pemKey)
require.NoError(t, err)
t.Run("valid token", func(t *testing.T) {
token := signConsumerToken(t, pemKey, validClaims())
claims, err := VerifyOIDCIDToken(token, "https://idp.example.com", "cloudreve", "nonce-1", jwks)
require.NoError(t, err)
require.Equal(t, "user@example.com", claims.Email)
require.Equal(t, "user-1", claims.Subject)
})
t.Run("wrong issuer", func(t *testing.T) {
token := signConsumerToken(t, pemKey, validClaims())
_, err := VerifyOIDCIDToken(token, "https://evil.example.com", "cloudreve", "nonce-1", jwks)
require.ErrorIs(t, err, ErrOIDCInvalidToken)
})
t.Run("wrong audience", func(t *testing.T) {
token := signConsumerToken(t, pemKey, validClaims())
_, err := VerifyOIDCIDToken(token, "https://idp.example.com", "other-client", "nonce-1", jwks)
require.ErrorIs(t, err, ErrOIDCInvalidToken)
})
t.Run("nonce mismatch", func(t *testing.T) {
token := signConsumerToken(t, pemKey, validClaims())
_, err := VerifyOIDCIDToken(token, "https://idp.example.com", "cloudreve", "other-nonce", jwks)
require.ErrorIs(t, err, ErrOIDCInvalidToken)
})
t.Run("expired token", func(t *testing.T) {
claims := validClaims()
claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(-time.Hour))
token := signConsumerToken(t, pemKey, claims)
_, err := VerifyOIDCIDToken(token, "https://idp.example.com", "cloudreve", "nonce-1", jwks)
require.ErrorIs(t, err, ErrOIDCInvalidToken)
})
t.Run("forged signature", func(t *testing.T) {
otherPEM := testRSAKeyPEM(t)
token := signConsumerToken(t, otherPEM, validClaims())
_, err := VerifyOIDCIDToken(token, "https://idp.example.com", "cloudreve", "nonce-1", jwks)
// Foreign key produces a different kid -> key not found in JWKS.
require.ErrorIs(t, err, ErrOIDCKeyNotFound)
})
t.Run("malformed token", func(t *testing.T) {
_, err := VerifyOIDCIDToken("not-a-jwt", "https://idp.example.com", "cloudreve", "nonce-1", jwks)
require.Error(t, err)
})
}
func TestOIDCDiscoveryValidate(t *testing.T) {
require.NoError(t, (&OIDCDiscovery{
Issuer: "https://idp.example.com",
AuthorizationEndpoint: "https://idp.example.com/authorize",
TokenEndpoint: "https://idp.example.com/token",
JWKSURI: "https://idp.example.com/jwks",
}).Validate())
require.ErrorIs(t, (&OIDCDiscovery{Issuer: "https://idp.example.com"}).Validate(), ErrOIDCDiscoveryFailed)
}