From d8a93d506a524efd15858dc2fc5c15b2f21eebaf Mon Sep 17 00:00:00 2001 From: Raffael Sahli Date: Tue, 3 Mar 2020 14:20:24 +0100 Subject: [PATCH] fix: native clients must be able to request a dynamic port rfc8252#section-7.3 fixes #284 --- authorize_helper.go | 37 ++++++++++++++++++++++++++++++++++++- authorize_helper_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/authorize_helper.go b/authorize_helper.go index 5c73e428..6aa087a5 100644 --- a/authorize_helper.go +++ b/authorize_helper.go @@ -23,6 +23,7 @@ package fosite import ( "net/url" + "regexp" "strings" "github.com/asaskevich/govalidator" @@ -83,7 +84,7 @@ func MatchRedirectURIWithClientRedirectURIs(rawurl string, client Client) (*url. // If no redirect_uri was given and the client has exactly one valid redirect_uri registered, use that instead return redirectURIFromClient, nil } - } else if rawurl != "" && StringInSlice(rawurl, client.GetRedirectURIs()) { + } else if rawurl != "" && isMatchingRedirectURI(rawurl, client.GetRedirectURIs()) { // If a redirect_uri was given and the clients knows it (simple string comparison!) // return it. if parsed, err := url.Parse(rawurl); err == nil && IsValidRedirectURI(parsed) { @@ -95,6 +96,40 @@ func MatchRedirectURIWithClientRedirectURIs(rawurl string, client Client) (*url. return nil, errors.WithStack(ErrInvalidRequest.WithHint(`The "redirect_uri" parameter does not match any of the OAuth 2.0 Client's pre-registered redirect urls.`)) } +// Match a requested redirect URI against a pool of registered client URIs +// +// Test a given redirect URI against a pool of URIs provided by a registered client. +// If the OAuth 2.0 Client has loopback URIs registered either an IPv4 URI http://127.0.0.1 or +// an IPv6 URI http://[::1] a client is allowed to request a dynamic port and the server MUST accept +// it as a valid redirection uri. +// +// https://tools.ietf.org/html/rfc8252#section-7.3 +// Native apps that are able to open a port on the loopback network +// interface without needing special permissions (typically, those on +// desktop operating systems) can use the loopback interface to receive +// the OAuth redirect. +// +// Loopback redirect URIs use the "http" scheme and are constructed with +// the loopback IP literal and whatever port the client is listening on. +func isMatchingRedirectURI(uri string, haystack []string) bool { + for _, b := range haystack { + l := strings.ToLower(b) + if l == strings.ToLower(uri) || isLoopbackURI(uri, l) { + return true + } + } + return false +} + +func isLoopbackURI(uri string, registeredURI string) bool { + if registeredURI != "http://127.0.0.1" && registeredURI != "http://[::1]" { + return false + } + + match, _ := regexp.MatchString("http://(127.0.0.1|\\[::1\\]):?(\\d+)?", uri) + return match +} + // IsValidRedirectURI validates a redirect_uri as specified in: // // * https://tools.ietf.org/html/rfc6749#section-3.1.2 diff --git a/authorize_helper_test.go b/authorize_helper_test.go index 71f786a6..6d1c5f0b 100644 --- a/authorize_helper_test.go +++ b/authorize_helper_test.go @@ -154,6 +154,46 @@ func TestDoesClientWhiteListRedirect(t *testing.T) { url: "https://bar.com/cb123", isError: true, }, + { + client: &DefaultClient{RedirectURIs: []string{"http://[::1]"}}, + url: "http://[::1]:1024", + isError: false, + expected: "http://[::1]:1024", + }, + { + client: &DefaultClient{RedirectURIs: []string{"http://[::1]"}}, + url: "http://[::1]:1024/cb", + isError: false, + expected: "http://[::1]:1024/cb", + }, + { + client: &DefaultClient{RedirectURIs: []string{"http://[::1]"}}, + url: "http://foo.bar/bar", + isError: true, + }, + { + client: &DefaultClient{RedirectURIs: []string{"http://127.0.0.1"}}, + url: "http://127.0.0.1:1024", + isError: false, + expected: "http://127.0.0.1:1024", + }, + { + client: &DefaultClient{RedirectURIs: []string{"http://127.0.0.1"}}, + url: "http://127.0.0.1:64000/cb", + isError: false, + expected: "http://127.0.0.1:64000/cb", + }, + { + client: &DefaultClient{RedirectURIs: []string{"http://127.0.0.1"}}, + url: "http://127.0.0.1", + isError: false, + expected: "http://127.0.0.1", + }, + { + client: &DefaultClient{RedirectURIs: []string{"http://127.0.0.1"}}, + url: "http://foo.bar/bar", + isError: true, + }, } { redir, err := MatchRedirectURIWithClientRedirectURIs(c.url, c.client) assert.Equal(t, c.isError, err != nil, "%d: %s", k, err)