diff --git a/auth/api/iam/metadata.go b/auth/api/iam/metadata.go index eb21d6008f..359ce06dda 100644 --- a/auth/api/iam/metadata.go +++ b/auth/api/iam/metadata.go @@ -18,7 +18,35 @@ package iam -import "net/url" +import ( + "github.com/nuts-foundation/nuts-node/core" + "net/url" +) + +const ( + // authzServerWellKnown is the well-known base path for the oauth authorization server metadata as defined in RFC8414 + authzServerWellKnown = "/.well-known/oauth-authorization-server" + // openidCredIssuerWellKnown is the well-known base path for the openID credential issuer metadata as defined in OpenID4VCI specification + openidCredIssuerWellKnown = "/.well-known/openid-credential-issuer" + // openidCredWalletWellKnown is the well-known path element we created for openid4vci to retrieve the oauth client metadata + openidCredWalletWellKnown = "/.well-known/openid-credential-wallet" +) + +// IssuerIdToWellKnown converts the OAuth2 Issuer identity to the specified well-known endpoint by inserting the well-known at the root of the path. +// It returns no url and an error when issuer is not a valid URL. +func IssuerIdToWellKnown(issuer string, wellKnown string, strictmode bool) (*url.URL, error) { + var issuerURL *url.URL + var err error + if strictmode { + issuerURL, err = core.ParsePublicURL(issuer, false, "https") + } else { + issuerURL, err = core.ParsePublicURL(issuer, true, "https", "http") + } + if err != nil { + return nil, err + } + return issuerURL.Parse(wellKnown + issuerURL.EscapedPath()) +} func authorizationServerMetadata(identity url.URL) OAuthAuthorizationServerMetadata { return OAuthAuthorizationServerMetadata{ diff --git a/auth/api/iam/metadata_test.go b/auth/api/iam/metadata_test.go index 023d64bda6..a657c52365 100644 --- a/auth/api/iam/metadata_test.go +++ b/auth/api/iam/metadata_test.go @@ -20,10 +20,50 @@ package iam import ( "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "net/url" "testing" ) +func TestIssuerIdToWellKnown(t *testing.T) { + t.Run("ok", func(t *testing.T) { + issuer := "https://nuts.nl/iam/id" + u, err := IssuerIdToWellKnown(issuer, authzServerWellKnown, true) + require.NoError(t, err) + assert.Equal(t, "https://nuts.nl/.well-known/oauth-authorization-server/iam/id", u.String()) + }) + t.Run("no path in issuer", func(t *testing.T) { + issuer := "https://nuts.nl" + u, err := IssuerIdToWellKnown(issuer, authzServerWellKnown, true) + require.NoError(t, err) + assert.Equal(t, "https://nuts.nl/.well-known/oauth-authorization-server", u.String()) + }) + t.Run("don't unescape path", func(t *testing.T) { + issuer := "https://nuts.nl/iam/%2E%2E/still-has-iam" + u, err := IssuerIdToWellKnown(issuer, authzServerWellKnown, true) + require.NoError(t, err) + assert.Equal(t, "https://nuts.nl/.well-known/oauth-authorization-server/iam/%2E%2E/still-has-iam", u.String()) + }) + t.Run("https in strictmode", func(t *testing.T) { + issuer := "http://nuts.nl/iam/id" + u, err := IssuerIdToWellKnown(issuer, authzServerWellKnown, true) + assert.ErrorContains(t, err, "scheme must be https") + assert.Nil(t, u) + }) + t.Run("no IP allowed", func(t *testing.T) { + issuer := "http://127.0.0.1/iam/id" + u, err := IssuerIdToWellKnown(issuer, authzServerWellKnown, false) + assert.ErrorContains(t, err, "hostname is IP") + assert.Nil(t, u) + }) + t.Run("invalid URL", func(t *testing.T) { + issuer := "http:// /iam/id" + u, err := IssuerIdToWellKnown(issuer, authzServerWellKnown, true) + assert.ErrorContains(t, err, "invalid character \" \" in host name") + assert.Nil(t, u) + }) +} + func Test_authorizationServerMetadata(t *testing.T) { identity := "https://example.com/iam/did:nuts:123" identityURL, _ := url.Parse(identity)