authenticate: fix expiring user info endpoint (#2976)

* authenticate: fix expiring user info endpoint

* add test
This commit is contained in:
Caleb Doxsey 2022-01-27 16:10:47 -07:00 committed by GitHub
parent fbdbe9c86f
commit 2f328e7de0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 122 additions and 37 deletions

View file

@ -16,7 +16,6 @@ import (
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/webauthnutil"
)
// ValidateOptions checks that configuration are complete and valid.
@ -125,25 +124,6 @@ func (a *Authenticate) updateProvider(cfg *config.Config) error {
return nil
}
func (a *Authenticate) getWebAuthnURL(values url.Values) (*url.URL, error) {
uri, err := a.options.Load().GetAuthenticateURL()
if err != nil {
return nil, err
}
uri = uri.ResolveReference(&url.URL{
Path: "/.pomerium/webauthn",
RawQuery: buildURLValues(values, url.Values{
urlutil.QueryDeviceType: {webauthnutil.DefaultDeviceType},
urlutil.QueryEnrollmentToken: nil,
urlutil.QueryRedirectURI: {uri.ResolveReference(&url.URL{
Path: "/.pomerium/device-enrolled",
}).String()},
}).Encode(),
})
return urlutil.NewSignedURL(a.state.Load().sharedKey, uri).Sign(), nil
}
// buildURLValues creates a new url.Values map by traversing the keys in `defaults` and using the values
// from `values` if they exist, otherwise the provided defaults
func buildURLValues(values, defaults url.Values) url.Values {

View file

@ -448,6 +448,19 @@ func (a *Authenticate) userInfo(w http.ResponseWriter, r *http.Request) error {
ctx, span := trace.StartSpan(r.Context(), "authenticate.userInfo")
defer span.End()
// if we came in with a redirect URI, save it to a cookie so it doesn't expire with the HMAC
if redirectURI := r.FormValue(urlutil.QueryRedirectURI); redirectURI != "" {
u := urlutil.GetAbsoluteURL(r)
u.RawQuery = ""
http.SetCookie(w, &http.Cookie{
Name: urlutil.QueryRedirectURI,
Value: redirectURI,
})
http.Redirect(w, r, u.String(), http.StatusFound)
return nil
}
state := a.state.Load()
s, err := a.getSessionFromCtx(ctx)
@ -626,23 +639,6 @@ func (a *Authenticate) revokeSession(ctx context.Context, w http.ResponseWriter,
return rawIDToken
}
func (a *Authenticate) getSignOutURL(r *http.Request) (*url.URL, error) {
uri, err := a.options.Load().GetAuthenticateURL()
if err != nil {
return nil, err
}
uri = uri.ResolveReference(&url.URL{
Path: "/.pomerium/sign_out",
})
if redirectURI := r.FormValue(urlutil.QueryRedirectURI); redirectURI != "" {
uri.RawQuery = (&url.Values{
urlutil.QueryRedirectURI: {redirectURI},
}).Encode()
}
return urlutil.NewSignedURL(a.state.Load().sharedKey, uri).Sign(), nil
}
func (a *Authenticate) getCurrentSession(ctx context.Context) (s *session.Session, isImpersonated bool, err error) {
client := a.state.Load().dataBrokerClient

57
authenticate/url.go Normal file
View file

@ -0,0 +1,57 @@
package authenticate
import (
"net/http"
"net/url"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/webauthnutil"
)
func (a *Authenticate) getRedirectURI(r *http.Request) (string, bool) {
if v := r.FormValue(urlutil.QueryRedirectURI); v != "" {
return v, true
}
if c, err := r.Cookie(urlutil.QueryRedirectURI); err == nil {
return c.Value, true
}
return "", false
}
func (a *Authenticate) getSignOutURL(r *http.Request) (*url.URL, error) {
uri, err := a.options.Load().GetAuthenticateURL()
if err != nil {
return nil, err
}
uri = uri.ResolveReference(&url.URL{
Path: "/.pomerium/sign_out",
})
if redirectURI, ok := a.getRedirectURI(r); ok {
uri.RawQuery = (&url.Values{
urlutil.QueryRedirectURI: {redirectURI},
}).Encode()
}
return urlutil.NewSignedURL(a.state.Load().sharedKey, uri).Sign(), nil
}
func (a *Authenticate) getWebAuthnURL(values url.Values) (*url.URL, error) {
uri, err := a.options.Load().GetAuthenticateURL()
if err != nil {
return nil, err
}
uri = uri.ResolveReference(&url.URL{
Path: "/.pomerium/webauthn",
RawQuery: buildURLValues(values, url.Values{
urlutil.QueryDeviceType: {webauthnutil.DefaultDeviceType},
urlutil.QueryEnrollmentToken: nil,
urlutil.QueryRedirectURI: {uri.ResolveReference(&url.URL{
Path: "/.pomerium/device-enrolled",
}).String()},
}).Encode(),
})
return urlutil.NewSignedURL(a.state.Load().sharedKey, uri).Sign(), nil
}

52
authenticate/url_test.go Normal file
View file

@ -0,0 +1,52 @@
package authenticate
import (
"net/http"
"net/url"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/pomerium/pomerium/internal/urlutil"
)
func TestAuthenticate_getRedirectURI(t *testing.T) {
t.Run("query", func(t *testing.T) {
r, err := http.NewRequest("GET", "https://www.example.com?"+(url.Values{
urlutil.QueryRedirectURI: {"https://www.example.com/redirect"},
}).Encode(), nil)
require.NoError(t, err)
a := new(Authenticate)
redirectURI, ok := a.getRedirectURI(r)
assert.True(t, ok)
assert.Equal(t, "https://www.example.com/redirect", redirectURI)
})
t.Run("form", func(t *testing.T) {
r, err := http.NewRequest("POST", "https://www.example.com", strings.NewReader((url.Values{
urlutil.QueryRedirectURI: {"https://www.example.com/redirect"},
}).Encode()))
require.NoError(t, err)
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
a := new(Authenticate)
redirectURI, ok := a.getRedirectURI(r)
assert.True(t, ok)
assert.Equal(t, "https://www.example.com/redirect", redirectURI)
})
t.Run("cookie", func(t *testing.T) {
r, err := http.NewRequest("GET", "https://www.example.com", nil)
require.NoError(t, err)
r.AddCookie(&http.Cookie{
Name: urlutil.QueryRedirectURI,
Value: "https://www.example.com/redirect",
})
a := new(Authenticate)
redirectURI, ok := a.getRedirectURI(r)
assert.True(t, ok)
assert.Equal(t, "https://www.example.com/redirect", redirectURI)
})
}