You've already forked oauth2-proxy
mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-11-29 22:48:19 +02:00
Move generic OIDC functionality to be available to all providers
This commit is contained in:
@@ -2,42 +2,18 @@ package providers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const accessToken = "access_token"
|
||||
const refreshToken = "refresh_token"
|
||||
const clientID = "https://test.myapp.com"
|
||||
const secret = "secret"
|
||||
|
||||
type idTokenClaims struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Phone string `json:"phone_number,omitempty"`
|
||||
Picture string `json:"picture,omitempty"`
|
||||
Groups interface{} `json:"groups,omitempty"`
|
||||
OtherGroups interface{} `json:"other_groups,omitempty"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
type redeemTokenResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
@@ -46,88 +22,12 @@ type redeemTokenResponse struct {
|
||||
IDToken string `json:"id_token,omitempty"`
|
||||
}
|
||||
|
||||
var defaultIDToken idTokenClaims = idTokenClaims{
|
||||
"Jane Dobbs",
|
||||
"janed@me.com",
|
||||
"+4798765432",
|
||||
"http://mugbook.com/janed/me.jpg",
|
||||
[]string{"test:a", "test:b"},
|
||||
[]string{"test:c", "test:d"},
|
||||
jwt.StandardClaims{
|
||||
Audience: "https://test.myapp.com",
|
||||
ExpiresAt: time.Now().Add(time.Duration(5) * time.Minute).Unix(),
|
||||
Id: "id-some-id",
|
||||
IssuedAt: time.Now().Unix(),
|
||||
Issuer: "https://issuer.example.com",
|
||||
NotBefore: 0,
|
||||
Subject: "123456789",
|
||||
},
|
||||
}
|
||||
|
||||
var customGroupClaimIDToken idTokenClaims = idTokenClaims{
|
||||
"Jane Dobbs",
|
||||
"janed@me.com",
|
||||
"+4798765432",
|
||||
"http://mugbook.com/janed/me.jpg",
|
||||
[]map[string]interface{}{
|
||||
{
|
||||
"groupId": "Admin Group Id",
|
||||
"roles": []string{"Admin"},
|
||||
},
|
||||
},
|
||||
[]string{"test:c", "test:d"},
|
||||
jwt.StandardClaims{
|
||||
Audience: "https://test.myapp.com",
|
||||
ExpiresAt: time.Now().Add(time.Duration(5) * time.Minute).Unix(),
|
||||
Id: "id-some-id",
|
||||
IssuedAt: time.Now().Unix(),
|
||||
Issuer: "https://issuer.example.com",
|
||||
NotBefore: 0,
|
||||
Subject: "123456789",
|
||||
},
|
||||
}
|
||||
|
||||
var minimalIDToken idTokenClaims = idTokenClaims{
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
[]string{},
|
||||
[]string{},
|
||||
jwt.StandardClaims{
|
||||
Audience: "https://test.myapp.com",
|
||||
ExpiresAt: time.Now().Add(time.Duration(5) * time.Minute).Unix(),
|
||||
Id: "id-some-id",
|
||||
IssuedAt: time.Now().Unix(),
|
||||
Issuer: "https://issuer.example.com",
|
||||
NotBefore: 0,
|
||||
Subject: "minimal",
|
||||
},
|
||||
}
|
||||
|
||||
type fakeKeySetStub struct{}
|
||||
|
||||
func (fakeKeySetStub) VerifySignature(_ context.Context, jwt string) (payload []byte, err error) {
|
||||
decodeString, err := base64.RawURLEncoding.DecodeString(strings.Split(jwt, ".")[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tokenClaims := &idTokenClaims{}
|
||||
err = json.Unmarshal(decodeString, tokenClaims)
|
||||
|
||||
if err != nil || tokenClaims.Id == "this-id-fails-validation" {
|
||||
return nil, fmt.Errorf("the validation failed for subject [%v]", tokenClaims.Subject)
|
||||
}
|
||||
|
||||
return decodeString, err
|
||||
}
|
||||
|
||||
func newOIDCProvider(serverURL *url.URL) *OIDCProvider {
|
||||
|
||||
providerData := &ProviderData{
|
||||
ProviderName: "oidc",
|
||||
ClientID: clientID,
|
||||
ClientSecret: secret,
|
||||
ClientID: oidcClientID,
|
||||
ClientSecret: oidcSecret,
|
||||
LoginURL: &url.URL{
|
||||
Scheme: serverURL.Scheme,
|
||||
Host: serverURL.Host,
|
||||
@@ -144,18 +44,17 @@ func newOIDCProvider(serverURL *url.URL) *OIDCProvider {
|
||||
Scheme: serverURL.Scheme,
|
||||
Host: serverURL.Host,
|
||||
Path: "/api"},
|
||||
Scope: "openid profile offline_access",
|
||||
Scope: "openid profile offline_access",
|
||||
EmailClaim: "email",
|
||||
GroupsClaim: "groups",
|
||||
Verifier: oidc.NewVerifier(
|
||||
"https://issuer.example.com",
|
||||
fakeKeySetStub{},
|
||||
&oidc.Config{ClientID: clientID},
|
||||
oidcIssuer,
|
||||
mockJWKS{},
|
||||
&oidc.Config{ClientID: oidcClientID},
|
||||
),
|
||||
}
|
||||
|
||||
p := &OIDCProvider{
|
||||
ProviderData: providerData,
|
||||
EmailClaim: "email",
|
||||
}
|
||||
p := &OIDCProvider{ProviderData: providerData}
|
||||
|
||||
return p
|
||||
}
|
||||
@@ -169,21 +68,6 @@ func newOIDCServer(body []byte) (*url.URL, *httptest.Server) {
|
||||
return u, s
|
||||
}
|
||||
|
||||
func newSignedTestIDToken(tokenClaims idTokenClaims) (string, error) {
|
||||
key, _ := rsa.GenerateKey(rand.Reader, 2048)
|
||||
standardClaims := jwt.NewWithClaims(jwt.SigningMethodRS256, tokenClaims)
|
||||
return standardClaims.SignedString(key)
|
||||
}
|
||||
|
||||
func newOauth2Token() *oauth2.Token {
|
||||
return &oauth2.Token{
|
||||
AccessToken: accessToken,
|
||||
TokenType: "Bearer",
|
||||
RefreshToken: refreshToken,
|
||||
Expiry: time.Time{}.Add(time.Duration(5) * time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
func newTestSetup(body []byte) (*httptest.Server, *OIDCProvider) {
|
||||
redeemURL, server := newOIDCServer(body)
|
||||
provider := newOIDCProvider(redeemURL)
|
||||
@@ -234,12 +118,6 @@ func TestOIDCProviderRedeem_custom_userid(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestOIDCProvider_EnrichSession(t *testing.T) {
|
||||
const (
|
||||
idToken = "Unchanged ID Token"
|
||||
accessToken = "Unchanged Access Token"
|
||||
refreshToken = "Unchanged Refresh Token"
|
||||
)
|
||||
|
||||
testCases := map[string]struct {
|
||||
ExistingSession *sessions.SessionState
|
||||
EmailClaim string
|
||||
@@ -550,8 +428,6 @@ func TestOIDCProviderRefreshSessionIfNeededWithIdToken(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestOIDCProviderCreateSessionFromToken(t *testing.T) {
|
||||
const profileURLEmail = "janed@me.com"
|
||||
|
||||
testCases := map[string]struct {
|
||||
IDToken idTokenClaims
|
||||
GroupsClaim string
|
||||
@@ -562,36 +438,35 @@ func TestOIDCProviderCreateSessionFromToken(t *testing.T) {
|
||||
"Default IDToken": {
|
||||
IDToken: defaultIDToken,
|
||||
GroupsClaim: "groups",
|
||||
ExpectedUser: defaultIDToken.Subject,
|
||||
ExpectedEmail: defaultIDToken.Email,
|
||||
ExpectedUser: "123456789",
|
||||
ExpectedEmail: "janed@me.com",
|
||||
ExpectedGroups: []string{"test:a", "test:b"},
|
||||
},
|
||||
"Minimal IDToken with no email claim": {
|
||||
IDToken: minimalIDToken,
|
||||
GroupsClaim: "groups",
|
||||
ExpectedUser: minimalIDToken.Subject,
|
||||
ExpectedEmail: minimalIDToken.Subject,
|
||||
ExpectedUser: "123456789",
|
||||
ExpectedEmail: "123456789",
|
||||
ExpectedGroups: []string{},
|
||||
},
|
||||
"Custom Groups Claim": {
|
||||
IDToken: defaultIDToken,
|
||||
GroupsClaim: "other_groups",
|
||||
ExpectedUser: defaultIDToken.Subject,
|
||||
ExpectedEmail: defaultIDToken.Email,
|
||||
GroupsClaim: "roles",
|
||||
ExpectedUser: "123456789",
|
||||
ExpectedEmail: "janed@me.com",
|
||||
ExpectedGroups: []string{"test:c", "test:d"},
|
||||
},
|
||||
"Custom Groups Claim2": {
|
||||
IDToken: customGroupClaimIDToken,
|
||||
"Complex Groups Claim": {
|
||||
IDToken: complexGroupsIDToken,
|
||||
GroupsClaim: "groups",
|
||||
ExpectedUser: customGroupClaimIDToken.Subject,
|
||||
ExpectedEmail: customGroupClaimIDToken.Email,
|
||||
ExpectedUser: "123456789",
|
||||
ExpectedEmail: "complex@claims.com",
|
||||
ExpectedGroups: []string{"{\"groupId\":\"Admin Group Id\",\"roles\":[\"Admin\"]}"},
|
||||
},
|
||||
}
|
||||
for testName, tc := range testCases {
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
jsonResp := []byte(fmt.Sprintf(`{"email":"%s"}`, profileURLEmail))
|
||||
server, provider := newTestSetup(jsonResp)
|
||||
server, provider := newTestSetup([]byte(`{}`))
|
||||
provider.GroupsClaim = tc.GroupsClaim
|
||||
defer server.Close()
|
||||
|
||||
@@ -610,40 +485,3 @@ func TestOIDCProviderCreateSessionFromToken(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOIDCProvider_findVerifiedIDToken(t *testing.T) {
|
||||
|
||||
server, provider := newTestSetup([]byte(""))
|
||||
|
||||
defer server.Close()
|
||||
|
||||
token := newOauth2Token()
|
||||
signedIDToken, _ := newSignedTestIDToken(defaultIDToken)
|
||||
tokenWithIDToken := token.WithExtra(map[string]interface{}{
|
||||
"id_token": signedIDToken,
|
||||
})
|
||||
|
||||
verifiedIDToken, err := provider.findVerifiedIDToken(context.Background(), tokenWithIDToken)
|
||||
assert.Equal(t, true, err == nil)
|
||||
if verifiedIDToken == nil {
|
||||
t.Fatal("verifiedIDToken is nil")
|
||||
}
|
||||
assert.Equal(t, defaultIDToken.Issuer, verifiedIDToken.Issuer)
|
||||
assert.Equal(t, defaultIDToken.Subject, verifiedIDToken.Subject)
|
||||
|
||||
// When the validation fails the response should be nil
|
||||
defaultIDToken.Id = "this-id-fails-validation"
|
||||
signedIDToken, _ = newSignedTestIDToken(defaultIDToken)
|
||||
tokenWithIDToken = token.WithExtra(map[string]interface{}{
|
||||
"id_token": signedIDToken,
|
||||
})
|
||||
|
||||
verifiedIDToken, err = provider.findVerifiedIDToken(context.Background(), tokenWithIDToken)
|
||||
assert.Equal(t, errors.New("failed to verify signature: the validation failed for subject [123456789]"), err)
|
||||
assert.Equal(t, true, verifiedIDToken == nil)
|
||||
|
||||
// When there is no id token in the oauth token
|
||||
verifiedIDToken, err = provider.findVerifiedIDToken(context.Background(), newOauth2Token())
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, true, verifiedIDToken == nil)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user