Skip to content

Commit

Permalink
fix tests
Browse files Browse the repository at this point in the history
  • Loading branch information
hf committed Oct 15, 2023
1 parent 1f3f8ea commit fdeeb99
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 6 deletions.
109 changes: 103 additions & 6 deletions internal/api/external_azure_test.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,105 @@
package api

import (
"context"
"crypto"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"time"

"github.com/coreos/go-oidc/v3/oidc"
jwt "github.com/golang-jwt/jwt"
"github.com/supabase/gotrue/internal/api/provider"
)

const (
azureUser string = `{"name":"Azure Test","email":"[email protected]","sub":"azuretestid"}`
azureUserNoEmail string = `{"name":"Azure Test","sub":"azuretestid"}`
)

func idTokenPrivateKey() *rsa.PrivateKey {
// #nosec
der, err := base64.StdEncoding.DecodeString("MIIEpAIBAAKCAQEAvklrFDsVgbhs3DOQICMqm4xdFoi/MHj/T6XH8S7wXWd0roqdWVarwCLV4y3DILkLre4PzNK+hEY5NAnoAKrsCMyyCb4Wdl8HCdJk4ojDqAig+DJw67imqZoxJMFJyIhfMJhwVK1V8GRUPATn855rygLo7wThahMJeEHNiJr3TtV6Rf35KSs7DuyoWIUSjISYabQozKqIvpdUpTpSqjlOQvjdAxggRyycBZSgLzjWhsA8metnAMO48bX4bgiHLR6Kzu/dfPyEVPfgeYpA2ebIY6GzIUxVS0yX8+ExA6jeLCkuepjLHuz5XCJtd6zzGDXr1eX7nA6ZIeUNdFbWRDnPawIDAQABAoIBABH4Qvl1HvHSJc2hvPGcAJER71SKc2uzcYDnCfu30BEyDO3Sv0tJiQyq/YHnt26mqviw66MPH9jD/PDyIou1mHa4RfPvlJV3IeYGjWprOfbrYbAuq0VHec24dv2el0YtwreHHcyRVfVOtDm6yODTzCAWqEKyNktbIuDNbgiBgetayaJecDRoFMF9TOCeMCL92iZytzAr7fi+JWtLkRS/GZRIBjbr8LJ/ueYoCRmIx3MIw0WdPp7v2ZfeRTxP7LxJZ+MAsrq2pstmZYP7K0305e0bCJX1HexfXLs2Ul7u8zaxrXL8zw4/9+/GMsAeU3ffCVnGz/RKL5+T6iuz2RotjFECgYEA+Xk7DGwRXfDg9xba1GVFGeiC4nybqZw/RfZKcz/RRJWSHRJV/ps1avtbca3B19rjI6rewZMO1NWNv/tI2BdXP8vAKUnI9OHJZ+J/eZzmqDE6qu0v0ddRFUDzCMWE0j8BjrUdy44n4NQgopcv14u0iyr9tuhGO6YXn2SuuvEkZokCgYEAw0PNnT55kpkEhXSp7An2hdBJEub9ST7hS6Kcd8let62/qUZ/t5jWigSkWC1A2bMtH55+LgudIFjiehwVzRs7jym2j4jkKZGonyAX1l9IWgXwKl7Pn49lEQH5Yk6MhnXdyLGoFTzXiUyk/fKvgXX7jow1bD3j6sAc8P495I7TyVMCgYAHg6VJrH+har37805IE3zPWPeIRuSRaUlmnBKGAigVfsPV6FV6w8YKIOQSOn+aNtecnWr0Pa+2rXAFllYNXDaej06Mb9KDvcFJRcM9MIKqEkGIIHjOQ0QH9drcKsbjZk5vs/jfxrpgxULuYstoHKclgff+aGSlK02O2YOB0f2csQKBgQCEC/MdNiWCpKXxFg7fB3HF1i/Eb56zjKlQu7uyKeQ6tG3bLEisQNg8Z5034Apt7gRC0KyluMbeHB2z1BBOLu9dBill8X3SOqVcTpiwKKlF76QVEx622YLQOJSMDXBscYK0+KchDY74U3N0JEzZcI7YPCrYcxYRJy+rLVNvn8LK7wKBgQDE8THsZ589e10F0zDBvPK56o8PJnPeH71sgdM2Co4oLzBJ6g0rpJOKfcc03fLHsoJVOAya9WZeIy6K8+WVdcPTadR07S4p8/tcK1eguu5qlmCUOzswrTKAaJoIHO7cddQp3nySIqgYtkGdHKuvlQDMQkEKJS0meOm+vdeAG2rkaA==")
if err != nil {
panic(err)
}

privateKey, err := x509.ParsePKCS1PrivateKey(der)
if err != nil {
panic(err)
}

return privateKey
}

func setupAzureOverrideVerifiers() {
provider.OverrideVerifiers[provider.IssuerAzureMicrosoft] = func(ctx context.Context, config *oidc.Config) *oidc.IDTokenVerifier {
pk := idTokenPrivateKey()

return oidc.NewVerifier(
provider.IssuerAzureMicrosoft,
&oidc.StaticKeySet{
PublicKeys: []crypto.PublicKey{
pk.PublicKey,
},
},
config,
)
}
}

func mintIDToken(user string) string {
var idToken struct {
Issuer string `json:"iss"`
IssuedAt int `json:"iat"`
ExpiresAt int `json:"exp"`
Audience string `json:"aud"`

Sub string `json:"sub,omitempty"`
Name string `json:"name,omitempty"`
Email string `josn:"email,omitempty"`
XmsEdov any `json:"xms_edov,omitempty"`
}

if err := json.Unmarshal([]byte(user), &idToken); err != nil {
panic(err)
}

now := time.Now()

idToken.Issuer = provider.IssuerAzureMicrosoft
idToken.IssuedAt = int(now.Unix())
idToken.ExpiresAt = int(now.Unix() + 60*60)
idToken.Audience = "testclientid"

header := base64.RawURLEncoding.EncodeToString([]byte(`{"typ":"JWT","alg":"RS256"}`))

data, err := json.Marshal(idToken)
if err != nil {
panic(err)
}

payload := base64.RawURLEncoding.EncodeToString(data)
sum := sha256.Sum256([]byte(header + "." + payload))

pk := idTokenPrivateKey()
sig, err := rsa.SignPKCS1v15(nil, pk, 0, sum[:])
if err != nil {
panic(err)
}

token := header + "." + payload + "." + base64.RawURLEncoding.EncodeToString(sig)

return token
}

func (ts *ExternalTestSuite) TestSignupExternalAzure() {
req := httptest.NewRequest(http.MethodGet, "http://localhost/authorize?provider=azure", nil)
w := httptest.NewRecorder()
Expand Down Expand Up @@ -48,11 +134,7 @@ func AzureTestSignupSetup(ts *ExternalTestSuite, tokenCount *int, userCount *int
ts.Equal(ts.Config.External.Azure.RedirectURI, r.FormValue("redirect_uri"))

w.Header().Add("Content-Type", "application/json")
fmt.Fprint(w, `{"access_token":"azure_token","expires_in":100000}`)
case "/oidc/userinfo":
*userCount++
w.Header().Add("Content-Type", "application/json")
fmt.Fprint(w, user)
fmt.Fprintf(w, `{"access_token":"azure_token","expires_in":100000,"id_token":%q}`, mintIDToken(user))
default:
w.WriteHeader(500)
ts.Fail("unknown azure oauth call %s", r.URL.Path)
Expand All @@ -66,6 +148,8 @@ func AzureTestSignupSetup(ts *ExternalTestSuite, tokenCount *int, userCount *int
}

func (ts *ExternalTestSuite) TestSignupExternalAzure_AuthorizationCode() {
setupAzureOverrideVerifiers()

ts.Config.DisableSignup = false
tokenCount, userCount := 0, 0
code := "authcode"
Expand All @@ -78,6 +162,8 @@ func (ts *ExternalTestSuite) TestSignupExternalAzure_AuthorizationCode() {
}

func (ts *ExternalTestSuite) TestSignupExternalAzureDisableSignupErrorWhenNoUser() {
setupAzureOverrideVerifiers()

ts.Config.DisableSignup = true
tokenCount, userCount := 0, 0
code := "authcode"
Expand All @@ -90,6 +176,8 @@ func (ts *ExternalTestSuite) TestSignupExternalAzureDisableSignupErrorWhenNoUser
}

func (ts *ExternalTestSuite) TestSignupExternalAzureDisableSignupErrorWhenNoEmail() {
setupAzureOverrideVerifiers()

ts.Config.DisableSignup = true
tokenCount, userCount := 0, 0
code := "authcode"
Expand All @@ -99,10 +187,11 @@ func (ts *ExternalTestSuite) TestSignupExternalAzureDisableSignupErrorWhenNoEmai
u := performAuthorization(ts, "azure", code, "")

assertAuthorizationFailure(ts, u, "Error getting user email from external provider", "server_error", "[email protected]")

}

func (ts *ExternalTestSuite) TestSignupExternalAzureDisableSignupSuccessWithPrimaryEmail() {
setupAzureOverrideVerifiers()

ts.Config.DisableSignup = true

ts.createUser("azuretestid", "[email protected]", "Azure Test", "http://example.com/avatar", "")
Expand All @@ -118,6 +207,8 @@ func (ts *ExternalTestSuite) TestSignupExternalAzureDisableSignupSuccessWithPrim
}

func (ts *ExternalTestSuite) TestInviteTokenExternalAzureSuccessWhenMatchingToken() {
setupAzureOverrideVerifiers()

// name should be populated from Azure API
ts.createUser("azuretestid", "[email protected]", "", "http://example.com/avatar", "invite_token")

Expand All @@ -132,6 +223,8 @@ func (ts *ExternalTestSuite) TestInviteTokenExternalAzureSuccessWhenMatchingToke
}

func (ts *ExternalTestSuite) TestInviteTokenExternalAzureErrorWhenNoMatchingToken() {
setupAzureOverrideVerifiers()

tokenCount, userCount := 0, 0
code := "authcode"
azureUser := `{"name":"Azure Test","avatar":{"href":"http://example.com/avatar"}}`
Expand All @@ -143,6 +236,8 @@ func (ts *ExternalTestSuite) TestInviteTokenExternalAzureErrorWhenNoMatchingToke
}

func (ts *ExternalTestSuite) TestInviteTokenExternalAzureErrorWhenWrongToken() {
setupAzureOverrideVerifiers()

ts.createUser("azuretestid", "[email protected]", "", "", "invite_token")

tokenCount, userCount := 0, 0
Expand All @@ -156,6 +251,8 @@ func (ts *ExternalTestSuite) TestInviteTokenExternalAzureErrorWhenWrongToken() {
}

func (ts *ExternalTestSuite) TestInviteTokenExternalAzureErrorWhenEmailDoesntMatch() {
setupAzureOverrideVerifiers()

ts.createUser("azuretestid", "[email protected]", "", "", "invite_token")

tokenCount, userCount := 0, 0
Expand Down
9 changes: 9 additions & 0 deletions internal/api/provider/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
)

const IssuerAzure = "https://login.microsoftonline.com/common/v2.0"
const IssuerAzureMicrosoft = "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0"

const (
defaultAzureAuthBase = "login.microsoftonline.com/common"
Expand Down Expand Up @@ -48,6 +49,12 @@ func NewAzureProvider(ext conf.OAuthProviderConfiguration, scopes string) (OAuth

if ext.URL != "" {
expectedIssuer = authHost + "/v2.0"

if !IsAzureIssuer(expectedIssuer) {
// in tests, the URL is a local server which should not
// be the expected issuer
expectedIssuer = ""
}
}

return &azureProvider{
Expand Down Expand Up @@ -94,6 +101,8 @@ func (g azureProvider) detectIDTokenIssuer(ctx context.Context, idToken string)
func (g azureProvider) GetUserData(ctx context.Context, tok *oauth2.Token) (*UserProvidedData, error) {
idToken := tok.Extra("id_token")

fmt.Printf("AZURE ID TOKEN %v\n", idToken)

if idToken != nil {
issuer, err := g.detectIDTokenIssuer(ctx, idToken.(string))
if err != nil {
Expand Down
11 changes: 11 additions & 0 deletions internal/api/provider/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"strconv"
"strings"
"time"

"github.com/coreos/go-oidc/v3/oidc"
"github.com/golang-jwt/jwt"
Expand All @@ -21,6 +22,10 @@ type ParseIDTokenOptions struct {
// used in tests.
var OverrideVerifiers = make(map[string]func(context.Context, *oidc.Config) *oidc.IDTokenVerifier)

// OverrideClock can be used to set a custom clock function to be used when
// parsing ID tokens. Should only be used in tests.
var OverrideClock func() time.Time

func ParseIDToken(ctx context.Context, provider *oidc.Provider, config *oidc.Config, idToken string, options ParseIDTokenOptions) (*oidc.IDToken, *UserProvidedData, error) {
if config == nil {
config = &oidc.Config{
Expand All @@ -29,6 +34,12 @@ func ParseIDToken(ctx context.Context, provider *oidc.Provider, config *oidc.Con
}
}

if OverrideClock != nil {
clonedConfig := *config
clonedConfig.Now = OverrideClock
config = &clonedConfig
}

verifier := provider.VerifierContext(ctx, config)
overrideVerifier, ok := OverrideVerifiers[provider.Endpoint().AuthURL]
if ok && overrideVerifier != nil {
Expand Down
30 changes: 30 additions & 0 deletions internal/api/provider/oidc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,48 @@ func googleIDTokenVerifier(ctx context.Context, config *oidc.Config) *oidc.IDTok
)
}

func azureIDTokenVerifier(ctx context.Context, config *oidc.Config) *oidc.IDTokenVerifier {
keyBytes, err := base64.RawURLEncoding.DecodeString("1djHqyNclRpJWtHCnkP5QWvDxozCTG_ZDnkEmudpcxjnYrVL4RVIwdNCBLAStg8Dob5OUyAlHcRFMCqGTW4HA6kHgIxyfiFsYCBDMHWd2-61N1cAS6S9SdXlWXkBQgU0Qj6q_yFYTRS7J-zI_jMLRQAlpowfDFM1vSTBIci7kqynV6pPOz4jMaDQevmSscEs-jz7e8YXAiiVpN588oBQ0jzQaTTx90WjgRP23mn8mPyabj8gcR3gLwKLsBUhlp1oZj7FopGp8z8LHuueJB_q_LOUa_gAozZ0lfoJxFimXgpgEK7GNVdMRsMH3mIl0A5oYN8f29RFwbG0rNO5ZQ1YWQ")
if err != nil {
panic(err)
}

n := big.NewInt(0)
n.SetBytes(keyBytes)

publicKey := &rsa.PublicKey{
N: n,
E: 65537,
}

return oidc.NewVerifier(
IssuerAzureMicrosoft,
&oidc.StaticKeySet{
PublicKeys: []crypto.PublicKey{publicKey},
},
config,
)
}

var realIDTokens map[string]realIDToken = map[string]realIDToken{
IssuerGoogle: {
AccessToken: "ya29.a0AWY7CklOn4TehiT4kA6osNP6e-pHErOY8X53T2oUe7Oqqwc3-uIJpoEgoZCUogewBuNWr-JFT2FK9s0E0oRSFtAfu0-uIDckBj5ca1pxnk0-zPkPZouqoIyl0AlIpQjIUEuyuQTYUay99kRajbHcFCR1VMbNcQaCgYKAQESARESFQG1tDrp1joUHupV5Rn8-nWDpKkmMw0165",
IDToken: "eyJhbGciOiJSUzI1NiIsImtpZCI6Ijg1YmE5MzEzZmQ3YTdkNGFmYTg0ODg0YWJjYzg0MDMwMDQzNjMxODAiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI5MTQ2NjY0MjA3NS03OWNwaWs4aWNxYzU4NjY5bjdtaXY5NjZsYmFwOTNhMi5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImF1ZCI6IjkxNDY2NjQyMDc1LTc5Y3BpazhpY3FjNTg2NjluN21pdjk2NmxiYXA5M2EyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwic3ViIjoiMTAzNzgzMTkwMTI2NDM5NzUxMjY5IiwiaGQiOiJzdXBhYmFzZS5pbyIsImVtYWlsIjoic3RvamFuQHN1cGFiYXNlLmlvIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF0X2hhc2giOiJlcGVWV244VmxWa28zd195Unk3UDZRIiwibmFtZSI6IlN0b2phbiBEaW1pdHJvdnNraSIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQWNIVHRka0dhWjVlcGtqT1dxSEF1UUV4N2cwRlBCeXJiQ2ZNUjVNTk5kYz1zOTYtYyIsImdpdmVuX25hbWUiOiJTdG9qYW4iLCJmYW1pbHlfbmFtZSI6IkRpbWl0cm92c2tpIiwibG9jYWxlIjoiZW4tR0IiLCJpYXQiOjE2ODY2NTk5MzIsImV4cCI6MTY4NjY2MzUzMn0.nKAN9BFSxvavXYfWX4fZHREYY_3O4uOFRFq1KU1NNrBOMq_CPpM8c8PV7ZhKQvGCjBthSjtxGWbcqT0ByA7RdpNW6kj5UpFxEPdhenZ-eO1FwiEVIC8uZpiX6J3Nr7fAqi1P0DVeB3Zr_GrtkS9MDhZNb3hE5NDkvjCulwP4gRBC-5Pn_aRJRESxYkr_naKiSSmVilkmNVjZO4orq6KuYlvWHKHZIRiUI1akt0gVr5GxsEpd_duzUU30yVSPiq8l6fgxvJn2hT0MHa77wo3hvlP0NyAoSE7Nh4tRSowB0Qq7_byDMUmNWfXh-Qqa2M6ywuJ-_3LTLNUJH-cwdm2tNQ",
Time: time.Unix(1686659933, 0), // 1 sec after iat
Verifier: googleIDTokenVerifier,
},
IssuerAzureMicrosoft: {
AccessToken: "access-token",
Time: time.Unix(1697277774, 0), // 1 sec after iat
IDToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlhvdVhMWVExVGlwNW9kWWFqaUN0RlZnVmFFcyJ9.eyJ2ZXIiOiIyLjAiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vOTE4ODA0MGQtNmM2Ny00YzViLWIxMTItMzZhMzA0YjY2ZGFkL3YyLjAiLCJzdWIiOiJBQUFBQUFBQUFBQUFBQUFBQUFBQUFCWkRuRDkxOTBfc2wxcTZwenZlRHZNIiwiYXVkIjoiYTBkOGY5NzItNTRhYy00YWJmLTkxNGMtNTIyMDE0YzQwMjJhIiwiZXhwIjoxNjk3MzY0NDczLCJpYXQiOjE2OTcyNzc3NzMsIm5iZiI6MTY5NzI3Nzc3MywiZW1haWwiOiJzZGltaXRyb3Zza2lAZ21haWwuY29tIiwidGlkIjoiOTE4ODA0MGQtNmM2Ny00YzViLWIxMTItMzZhMzA0YjY2ZGFkIiwieG1zX2Vkb3YiOiIxIiwiYWlvIjoiRHBQV3lZSnRJcUl5OHpyVjROIUlIdGtFa09BMDhPS29lZ1RkYmZQUEVPYmxtYk9ESFQ0cGJVcVI1cExraENyWWZ6bUgzb3A1RzN5RGp2M0tNZ0Rad29lQ1FjKmVueldyb21iQ3BuKkR6OEpQOGMxU3pEVG1TbGp4U3U3UnVLTXNZSjRvS1lDazFBSVcqUUNUTmlMWkpUKlN3WWZQcjZBTW9IejFEZ3pBZEFkbk9uWiFHNUNFeEtQalBxcHRuVmpUZlEkJCJ9.CskICxOaeqd4SkiPdWEHJKZVdhAdgzM5SN7K7FYi0dguQH1-v6XTetDIoEsBn0GZoozXjbG2GgkFcVhhBvNA0ZrDIr4KcjfnJ5-7rwX3AtxdQ3umrHRlGu3jlmbDOtWzPWNMLLRXfR1Mm3pHEUvlzqmk3Ffh4TuAmXID-fb-Xmfuuv1k0UsZ5mlr_3ybTPVZk-Lj0bqkR1L5Zzt4HjgfpchRryJ3Y24b4dDsSjg7mgE_5JivgjhtVef5OnqYhKUF1DTy2pFysFO_eRliK6qjouYeZnQOJnWHP1MgpySAOQ3sVcwvE4P9g7V3QouxByZPv-g99N1K4GwZrtdm46gtTQ",
Verifier: azureIDTokenVerifier,
},
}

func TestParseIDToken(t *testing.T) {
defer func() {
OverrideVerifiers = make(map[string]func(context.Context, *oidc.Config) *oidc.IDTokenVerifier)
OverrideClock = nil
}()

// note that this test can fail if/when the issuers rotate their
Expand Down

0 comments on commit fdeeb99

Please sign in to comment.