mirror of
https://github.com/pomerium/pomerium.git
synced 2025-06-01 02:12:50 +02:00
feature/databroker: user data and session refactor project (#926)
* databroker: add databroker, identity manager, update cache (#864) * databroker: add databroker, identity manager, update cache * fix cache tests * directory service (#885) * directory: add google and okta * add onelogin * add directory provider * initialize before sync, upate google provider, remove dead code * add azure provider * fix azure provider * fix gitlab * add gitlab test, fix azure test * hook up okta * remove dead code * fix tests * fix flaky test * authorize: use databroker data for rego policy (#904) * wip * add directory provider * initialize before sync, upate google provider, remove dead code * fix flaky test * update authorize to use databroker data * implement signed jwt * wait for session and user to appear * fix test * directory service (#885) * directory: add google and okta * add onelogin * add directory provider * initialize before sync, upate google provider, remove dead code * add azure provider * fix azure provider * fix gitlab * add gitlab test, fix azure test * hook up okta * remove dead code * fix tests * fix flaky test * remove log line * only redirect when no session id exists * prepare rego query as part of create * return on ctx done * retry on disconnect for sync * move jwt signing * use != * use parent ctx for wait * remove session state, remove logs * rename function * add log message * pre-allocate slice * use errgroup * return nil on eof for sync * move check * disable timeout on gRPC requests in envoy * fix gitlab test * use v4 backoff * authenticate: databroker changes (#914) * wip * add directory provider * initialize before sync, upate google provider, remove dead code * fix flaky test * update authorize to use databroker data * implement signed jwt * wait for session and user to appear * fix test * directory service (#885) * directory: add google and okta * add onelogin * add directory provider * initialize before sync, upate google provider, remove dead code * add azure provider * fix azure provider * fix gitlab * add gitlab test, fix azure test * hook up okta * remove dead code * fix tests * fix flaky test * remove log line * only redirect when no session id exists * prepare rego query as part of create * return on ctx done * retry on disconnect for sync * move jwt signing * use != * use parent ctx for wait * remove session state, remove logs * rename function * add log message * pre-allocate slice * use errgroup * return nil on eof for sync * move check * disable timeout on gRPC requests in envoy * fix dashboard * delete session on logout * permanently delete sessions once they are marked as deleted * remove permanent delete * fix tests * remove groups and refresh test * databroker: remove dead code, rename cache url, move dashboard (#925) * wip * add directory provider * initialize before sync, upate google provider, remove dead code * fix flaky test * update authorize to use databroker data * implement signed jwt * wait for session and user to appear * fix test * directory service (#885) * directory: add google and okta * add onelogin * add directory provider * initialize before sync, upate google provider, remove dead code * add azure provider * fix azure provider * fix gitlab * add gitlab test, fix azure test * hook up okta * remove dead code * fix tests * fix flaky test * remove log line * only redirect when no session id exists * prepare rego query as part of create * return on ctx done * retry on disconnect for sync * move jwt signing * use != * use parent ctx for wait * remove session state, remove logs * rename function * add log message * pre-allocate slice * use errgroup * return nil on eof for sync * move check * disable timeout on gRPC requests in envoy * fix dashboard * delete session on logout * permanently delete sessions once they are marked as deleted * remove permanent delete * fix tests * remove cache service * remove kv * remove refresh docs * remove obsolete cache docs * add databroker url option * cache: use memberlist to detect multiple instances * add databroker service url * remove cache service * remove kv * remove refresh docs * remove obsolete cache docs * add databroker url option * cache: use memberlist to detect multiple instances * add databroker service url * wip * remove groups and refresh test * fix redirect, signout * remove databroker client from proxy * remove unused method * remove user dashboard test * handle missing session ids * session: reject sessions with no id * sessions: invalidate old sessions via databroker server version (#930) * session: add a version field tied to the databroker server version that can be used to invalidate sessions * fix tests * add log * authenticate: create user record immediately, call "get" directly in authorize (#931)
This commit is contained in:
parent
39cdb31170
commit
dbd7f55b20
115 changed files with 8479 additions and 3584 deletions
|
@ -1,6 +1,7 @@
|
|||
package authenticate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -11,13 +12,15 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
"github.com/pomerium/pomerium/internal/encoding/jws"
|
||||
"github.com/pomerium/pomerium/internal/encoding/mock"
|
||||
"github.com/pomerium/pomerium/internal/frontend"
|
||||
mock_cache "github.com/pomerium/pomerium/internal/grpc/cache/mock"
|
||||
"github.com/pomerium/pomerium/internal/grpc/databroker"
|
||||
"github.com/pomerium/pomerium/internal/grpc/session"
|
||||
"github.com/pomerium/pomerium/internal/httputil"
|
||||
"github.com/pomerium/pomerium/internal/identity"
|
||||
"github.com/pomerium/pomerium/internal/identity/oidc"
|
||||
|
@ -27,8 +30,8 @@ import (
|
|||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/oauth2"
|
||||
|
@ -120,27 +123,25 @@ func TestAuthenticate_SignIn(t *testing.T) {
|
|||
encoder encoding.MarshalUnmarshaler
|
||||
wantCode int
|
||||
}{
|
||||
{"good", "https", "corp.example.example", map[string]string{urlutil.QueryRedirectURI: "https://dst.some.example/"}, &mstore.Store{Session: &sessions.State{Email: "user@pomerium.io"}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound},
|
||||
{"good alternate port", "https", "corp.example.example:8443", map[string]string{urlutil.QueryRedirectURI: "https://dst.some.example/"}, &mstore.Store{Session: &sessions.State{Email: "user@pomerium.io"}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound},
|
||||
{"session not valid", "https", "corp.example.example", map[string]string{urlutil.QueryRedirectURI: "https://dst.some.example/"}, &mstore.Store{Session: &sessions.State{Email: "user@pomerium.io"}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound},
|
||||
{"bad redirect uri query", "", "corp.example.example", map[string]string{urlutil.QueryRedirectURI: "^^^"}, &mstore.Store{Session: &sessions.State{Email: "user@pomerium.io"}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusBadRequest},
|
||||
{"bad marshal", "https", "corp.example.example", map[string]string{urlutil.QueryRedirectURI: "https://dst.some.example/"}, &mstore.Store{Session: &sessions.State{Email: "user@pomerium.io"}}, identity.MockProvider{}, &mock.Encoder{MarshalError: errors.New("error")}, http.StatusBadRequest},
|
||||
{"good", "https", "corp.example.example", map[string]string{urlutil.QueryRedirectURI: "https://dst.some.example/"}, &mstore.Store{Session: &sessions.State{}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound},
|
||||
{"good alternate port", "https", "corp.example.example:8443", map[string]string{urlutil.QueryRedirectURI: "https://dst.some.example/"}, &mstore.Store{Session: &sessions.State{}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound},
|
||||
{"session not valid", "https", "corp.example.example", map[string]string{urlutil.QueryRedirectURI: "https://dst.some.example/"}, &mstore.Store{Session: &sessions.State{}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound},
|
||||
{"bad redirect uri query", "", "corp.example.example", map[string]string{urlutil.QueryRedirectURI: "^^^"}, &mstore.Store{Session: &sessions.State{}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusBadRequest},
|
||||
{"bad marshal", "https", "corp.example.example", map[string]string{urlutil.QueryRedirectURI: "https://dst.some.example/"}, &mstore.Store{Session: &sessions.State{}}, identity.MockProvider{}, &mock.Encoder{MarshalError: errors.New("error")}, http.StatusBadRequest},
|
||||
{"session error", "https", "corp.example.example", map[string]string{urlutil.QueryRedirectURI: "https://dst.some.example/"}, &mstore.Store{LoadError: errors.New("error")}, identity.MockProvider{}, &mock.Encoder{}, http.StatusBadRequest},
|
||||
{"good with different programmatic redirect", "https", "corp.example.example", map[string]string{urlutil.QueryRedirectURI: "https://dst.some.example/", urlutil.QueryCallbackURI: "https://some.example"}, &mstore.Store{Session: &sessions.State{Email: "user@pomerium.io"}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound},
|
||||
{"encrypted encoder error", "https", "corp.example.example", map[string]string{urlutil.QueryRedirectURI: "https://dst.some.example/", urlutil.QueryCallbackURI: "https://some.example"}, &mstore.Store{Session: &sessions.State{Email: "user@pomerium.io"}}, identity.MockProvider{}, &mock.Encoder{MarshalError: errors.New("error")}, http.StatusBadRequest},
|
||||
{"good with callback uri set", "https", "corp.example.example", map[string]string{urlutil.QueryCallbackURI: "https://some.example/", urlutil.QueryRedirectURI: "https://dst.some.example/"}, &mstore.Store{Session: &sessions.State{Email: "user@pomerium.io"}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound},
|
||||
{"bad callback uri set", "https", "corp.example.example", map[string]string{urlutil.QueryCallbackURI: "^", urlutil.QueryRedirectURI: "https://dst.some.example/"}, &mstore.Store{Session: &sessions.State{Email: "user@pomerium.io"}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusBadRequest},
|
||||
{"good programmatic request", "https", "corp.example.example", map[string]string{urlutil.QueryIsProgrammatic: "true", urlutil.QueryRedirectURI: "https://dst.some.example/"}, &mstore.Store{Session: &sessions.State{Email: "user@pomerium.io"}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound},
|
||||
{"good additional audience", "https", "corp.example.example", map[string]string{urlutil.QueryForwardAuth: "x.y.z", urlutil.QueryRedirectURI: "https://dst.some.example/"}, &mstore.Store{Session: &sessions.State{Email: "user@pomerium.io"}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound},
|
||||
{"good user impersonate", "https", "corp.example.example", map[string]string{urlutil.QueryImpersonateAction: "set", urlutil.QueryRedirectURI: "https://dst.some.example/"}, &mstore.Store{Session: &sessions.State{Email: "user@pomerium.io"}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound},
|
||||
{"bad user impersonate save failure", "https", "corp.example.example", map[string]string{urlutil.QueryImpersonateAction: "set", urlutil.QueryRedirectURI: "https://dst.some.example/"}, &mstore.Store{SaveError: errors.New("err"), Session: &sessions.State{Email: "user@pomerium.io"}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusBadRequest},
|
||||
{"good with different programmatic redirect", "https", "corp.example.example", map[string]string{urlutil.QueryRedirectURI: "https://dst.some.example/", urlutil.QueryCallbackURI: "https://some.example"}, &mstore.Store{Session: &sessions.State{}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound},
|
||||
{"encrypted encoder error", "https", "corp.example.example", map[string]string{urlutil.QueryRedirectURI: "https://dst.some.example/", urlutil.QueryCallbackURI: "https://some.example"}, &mstore.Store{Session: &sessions.State{}}, identity.MockProvider{}, &mock.Encoder{MarshalError: errors.New("error")}, http.StatusBadRequest},
|
||||
{"good with callback uri set", "https", "corp.example.example", map[string]string{urlutil.QueryCallbackURI: "https://some.example/", urlutil.QueryRedirectURI: "https://dst.some.example/"}, &mstore.Store{Session: &sessions.State{}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound},
|
||||
{"bad callback uri set", "https", "corp.example.example", map[string]string{urlutil.QueryCallbackURI: "^", urlutil.QueryRedirectURI: "https://dst.some.example/"}, &mstore.Store{Session: &sessions.State{}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusBadRequest},
|
||||
{"good programmatic request", "https", "corp.example.example", map[string]string{urlutil.QueryIsProgrammatic: "true", urlutil.QueryRedirectURI: "https://dst.some.example/"}, &mstore.Store{Session: &sessions.State{}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound},
|
||||
{"good additional audience", "https", "corp.example.example", map[string]string{urlutil.QueryForwardAuth: "x.y.z", urlutil.QueryRedirectURI: "https://dst.some.example/"}, &mstore.Store{Session: &sessions.State{}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound},
|
||||
{"good user impersonate", "https", "corp.example.example", map[string]string{urlutil.QueryImpersonateAction: "set", urlutil.QueryRedirectURI: "https://dst.some.example/"}, &mstore.Store{Session: &sessions.State{}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusFound},
|
||||
{"bad user impersonate save failure", "https", "corp.example.example", map[string]string{urlutil.QueryImpersonateAction: "set", urlutil.QueryRedirectURI: "https://dst.some.example/"}, &mstore.Store{SaveError: errors.New("err"), Session: &sessions.State{}}, identity.MockProvider{}, &mock.Encoder{}, http.StatusBadRequest},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
mc := mock_cache.NewMockCacher(ctrl)
|
||||
mc.EXPECT().Get(gomock.Any(), gomock.Any()).Return([]byte("hi"), nil).AnyTimes()
|
||||
|
||||
a := &Authenticate{
|
||||
sessionStore: tt.session,
|
||||
|
@ -154,7 +155,25 @@ func TestAuthenticate_SignIn(t *testing.T) {
|
|||
Name: "cookie",
|
||||
Domain: "foo",
|
||||
},
|
||||
cacheClient: mc,
|
||||
dataBrokerClient: mockDataBrokerServiceClient{
|
||||
get: func(ctx context.Context, in *databroker.GetRequest, opts ...grpc.CallOption) (*databroker.GetResponse, error) {
|
||||
data, err := ptypes.MarshalAny(&session.Session{
|
||||
Id: "SESSION_ID",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &databroker.GetResponse{
|
||||
Record: &databroker.Record{
|
||||
Version: "0001",
|
||||
Type: data.GetTypeUrl(),
|
||||
Id: "SESSION_ID",
|
||||
Data: data,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
uri := &url.URL{Scheme: tt.scheme, Host: tt.host}
|
||||
|
||||
|
@ -202,24 +221,21 @@ func TestAuthenticate_SignOut(t *testing.T) {
|
|||
wantCode int
|
||||
wantBody string
|
||||
}{
|
||||
{"good post", http.MethodPost, nil, "https://corp.pomerium.io/", "sig", "ts", identity.MockProvider{LogOutResponse: (*uriParseHelper("https://microsoft.com"))}, &mstore.Store{Encrypted: true, Session: &sessions.State{Email: "user@pomerium.io"}}, http.StatusFound, ""},
|
||||
{"failed revoke", http.MethodPost, nil, "https://corp.pomerium.io/", "sig", "ts", identity.MockProvider{RevokeError: errors.New("OH NO")}, &mstore.Store{Encrypted: true, Session: &sessions.State{Email: "user@pomerium.io"}}, http.StatusFound, ""},
|
||||
{"load session error", http.MethodPost, errors.New("error"), "https://corp.pomerium.io/", "sig", "ts", identity.MockProvider{RevokeError: errors.New("OH NO")}, &mstore.Store{Encrypted: true, Session: &sessions.State{Email: "user@pomerium.io"}}, http.StatusFound, ""},
|
||||
{"bad redirect uri", http.MethodPost, nil, "corp.pomerium.io/", "sig", "ts", identity.MockProvider{LogOutError: oidc.ErrSignoutNotImplemented}, &mstore.Store{Encrypted: true, Session: &sessions.State{Email: "user@pomerium.io"}}, http.StatusFound, ""},
|
||||
{"good post", http.MethodPost, nil, "https://corp.pomerium.io/", "sig", "ts", identity.MockProvider{LogOutResponse: (*uriParseHelper("https://microsoft.com"))}, &mstore.Store{Encrypted: true, Session: &sessions.State{}}, http.StatusFound, ""},
|
||||
{"failed revoke", http.MethodPost, nil, "https://corp.pomerium.io/", "sig", "ts", identity.MockProvider{RevokeError: errors.New("OH NO")}, &mstore.Store{Encrypted: true, Session: &sessions.State{}}, http.StatusFound, ""},
|
||||
{"load session error", http.MethodPost, errors.New("error"), "https://corp.pomerium.io/", "sig", "ts", identity.MockProvider{RevokeError: errors.New("OH NO")}, &mstore.Store{Encrypted: true, Session: &sessions.State{}}, http.StatusFound, ""},
|
||||
{"bad redirect uri", http.MethodPost, nil, "corp.pomerium.io/", "sig", "ts", identity.MockProvider{LogOutError: oidc.ErrSignoutNotImplemented}, &mstore.Store{Encrypted: true, Session: &sessions.State{}}, http.StatusFound, ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
mc := mock_cache.NewMockCacher(ctrl)
|
||||
mc.EXPECT().Get(gomock.Any(), gomock.Any()).Return([]byte("hi"), nil).AnyTimes()
|
||||
a := &Authenticate{
|
||||
sessionStore: tt.sessionStore,
|
||||
provider: tt.provider,
|
||||
encryptedEncoder: mock.Encoder{},
|
||||
templates: template.Must(frontend.NewTemplates()),
|
||||
sharedEncoder: mock.Encoder{},
|
||||
cacheClient: mc,
|
||||
}
|
||||
u, _ := url.Parse("/sign_out")
|
||||
params, _ := url.ParseQuery(u.RawQuery)
|
||||
|
@ -292,9 +308,6 @@ func TestAuthenticate_OAuthCallback(t *testing.T) {
|
|||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
mc := mock_cache.NewMockCacher(ctrl)
|
||||
mc.EXPECT().Get(gomock.Any(), gomock.Any()).Return([]byte("hi"), nil).AnyTimes()
|
||||
mc.EXPECT().Set(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
||||
aead, err := chacha20poly1305.NewX(cryptutil.NewKey())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -309,7 +322,6 @@ func TestAuthenticate_OAuthCallback(t *testing.T) {
|
|||
sessionStore: tt.session,
|
||||
provider: tt.provider,
|
||||
cookieCipher: aead,
|
||||
cacheClient: mc,
|
||||
encryptedEncoder: signer,
|
||||
}
|
||||
u, _ := url.Parse("/oauthGet")
|
||||
|
@ -363,20 +375,59 @@ func TestAuthenticate_SessionValidatorMiddleware(t *testing.T) {
|
|||
|
||||
wantStatus int
|
||||
}{
|
||||
{"good", nil, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, nil, identity.MockProvider{RefreshResponse: oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}, http.StatusOK},
|
||||
{"invalid session", nil, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, errors.New("hi"), identity.MockProvider{}, http.StatusFound},
|
||||
{"good refresh expired", nil, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, nil, identity.MockProvider{RefreshResponse: oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}, http.StatusOK},
|
||||
{"expired,refresh error", nil, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, sessions.ErrExpired, identity.MockProvider{RefreshError: errors.New("error")}, http.StatusFound},
|
||||
{"expired,save error", nil, &mstore.Store{SaveError: errors.New("error"), Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, sessions.ErrExpired, identity.MockProvider{RefreshResponse: oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}, http.StatusFound},
|
||||
{"expired XHR,refresh error", map[string]string{"X-Requested-With": "XmlHttpRequest"}, &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}}, sessions.ErrExpired, identity.MockProvider{RefreshError: errors.New("error")}, http.StatusUnauthorized},
|
||||
{
|
||||
"good",
|
||||
nil,
|
||||
&mstore.Store{Session: &sessions.State{Version: "v1", ID: "xyz", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}},
|
||||
nil,
|
||||
identity.MockProvider{RefreshResponse: oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}},
|
||||
http.StatusOK,
|
||||
},
|
||||
{
|
||||
"invalid session",
|
||||
nil,
|
||||
&mstore.Store{Session: &sessions.State{Version: "v1", ID: "xyz", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}},
|
||||
errors.New("hi"),
|
||||
identity.MockProvider{},
|
||||
http.StatusFound,
|
||||
},
|
||||
{
|
||||
"good refresh expired",
|
||||
nil,
|
||||
&mstore.Store{Session: &sessions.State{Version: "v1", ID: "xyz", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}},
|
||||
nil,
|
||||
identity.MockProvider{RefreshResponse: oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}},
|
||||
http.StatusOK,
|
||||
},
|
||||
{
|
||||
"expired,refresh error",
|
||||
nil,
|
||||
&mstore.Store{Session: &sessions.State{Version: "v1", ID: "xyz", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}},
|
||||
sessions.ErrExpired,
|
||||
identity.MockProvider{RefreshError: errors.New("error")},
|
||||
http.StatusFound,
|
||||
},
|
||||
{
|
||||
"expired,save error",
|
||||
nil,
|
||||
&mstore.Store{SaveError: errors.New("error"), Session: &sessions.State{Version: "v1", ID: "xyz", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}},
|
||||
sessions.ErrExpired,
|
||||
identity.MockProvider{RefreshResponse: oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}},
|
||||
http.StatusFound,
|
||||
},
|
||||
{
|
||||
"expired XHR,refresh error",
|
||||
map[string]string{"X-Requested-With": "XmlHttpRequest"},
|
||||
&mstore.Store{Session: &sessions.State{Version: "v1", ID: "xyz", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))}},
|
||||
sessions.ErrExpired,
|
||||
identity.MockProvider{RefreshError: errors.New("error")},
|
||||
http.StatusUnauthorized,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
mc := mock_cache.NewMockCacher(ctrl)
|
||||
mc.EXPECT().Get(gomock.Any(), gomock.Any()).Return([]byte("hi"), nil).AnyTimes()
|
||||
mc.EXPECT().Set(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
||||
|
||||
aead, err := chacha20poly1305.NewX(cryptutil.NewKey())
|
||||
if err != nil {
|
||||
|
@ -394,8 +445,7 @@ func TestAuthenticate_SessionValidatorMiddleware(t *testing.T) {
|
|||
provider: tt.provider,
|
||||
cookieCipher: aead,
|
||||
encryptedEncoder: signer,
|
||||
cacheClient: mc,
|
||||
sharedEncoder: mock.Encoder{},
|
||||
sharedEncoder: signer,
|
||||
}
|
||||
r := httptest.NewRequest("GET", "/", nil)
|
||||
state, err := tt.session.LoadSession(r)
|
||||
|
@ -417,178 +467,7 @@ func TestAuthenticate_SessionValidatorMiddleware(t *testing.T) {
|
|||
got := a.VerifySession(fn)
|
||||
got.ServeHTTP(w, r)
|
||||
if status := w.Code; status != tt.wantStatus {
|
||||
t.Errorf("VerifySession() error = %v, wantErr %v\n%v", w.Result().StatusCode, tt.wantStatus, w.Body.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthenticate_RefreshAPI(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
session sessions.SessionStore
|
||||
ctxError error
|
||||
|
||||
provider identity.Authenticator
|
||||
secretEncoder encoding.MarshalUnmarshaler
|
||||
sharedEncoder encoding.MarshalUnmarshaler
|
||||
|
||||
wantStatus int
|
||||
}{
|
||||
{"good", &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, nil, identity.MockProvider{RefreshResponse: oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}, mock.Encoder{MarshalResponse: []byte("ok")}, mock.Encoder{MarshalResponse: []byte("ok")}, http.StatusOK},
|
||||
{"refresh error", &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, nil, identity.MockProvider{RefreshError: errors.New("error")}, mock.Encoder{MarshalResponse: []byte("ok")}, mock.Encoder{MarshalResponse: []byte("ok")}, http.StatusInternalServerError},
|
||||
{"session is not refreshable error", &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, errors.New("session error"), identity.MockProvider{RefreshResponse: oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}, mock.Encoder{MarshalResponse: []byte("ok")}, mock.Encoder{MarshalResponse: []byte("ok")}, http.StatusBadRequest},
|
||||
{"secret encoder failed", &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, nil, identity.MockProvider{RefreshResponse: oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}, mock.Encoder{MarshalError: errors.New("error")}, mock.Encoder{MarshalResponse: []byte("ok")}, http.StatusInternalServerError},
|
||||
{"shared encoder failed", &mstore.Store{Session: &sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}}, nil, identity.MockProvider{RefreshResponse: oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}}, mock.Encoder{MarshalResponse: []byte("ok")}, mock.Encoder{MarshalError: errors.New("error")}, http.StatusInternalServerError},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
mc := mock_cache.NewMockCacher(ctrl)
|
||||
mc.EXPECT().Get(gomock.Any(), gomock.Any()).Return([]byte("hi"), nil).AnyTimes()
|
||||
mc.EXPECT().Set(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
||||
|
||||
aead, err := chacha20poly1305.NewX(cryptutil.NewKey())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a := Authenticate{
|
||||
sharedKey: cryptutil.NewBase64Key(),
|
||||
cookieSecret: cryptutil.NewKey(),
|
||||
RedirectURL: uriParseHelper("https://authenticate.corp.beyondperimeter.com"),
|
||||
encryptedEncoder: tt.secretEncoder,
|
||||
sharedEncoder: tt.sharedEncoder,
|
||||
sessionStore: tt.session,
|
||||
provider: tt.provider,
|
||||
cookieCipher: aead,
|
||||
cacheClient: mc,
|
||||
}
|
||||
r := httptest.NewRequest("GET", "/", nil)
|
||||
state, _ := tt.session.LoadSession(r)
|
||||
ctx := r.Context()
|
||||
ctx = sessions.NewContext(ctx, state, tt.ctxError)
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
r.Header.Set("Accept", "application/json")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
httputil.HandlerFunc(a.RefreshAPI).ServeHTTP(w, r)
|
||||
if status := w.Code; status != tt.wantStatus {
|
||||
t.Errorf("VerifySession() error = %v, wantErr %v\n%v", w.Result().StatusCode, tt.wantStatus, w.Body.String())
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthenticate_Refresh(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
session *sessions.State
|
||||
at *oauth2.Token
|
||||
|
||||
provider identity.Authenticator
|
||||
secretEncoder encoding.MarshalUnmarshaler
|
||||
|
||||
wantStatus int
|
||||
}{
|
||||
{"good",
|
||||
&sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))},
|
||||
&oauth2.Token{AccessToken: "mock", Expiry: time.Now().Add(10 * time.Minute)},
|
||||
identity.MockProvider{RefreshResponse: oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}},
|
||||
mock.Encoder{MarshalResponse: []byte("ok")},
|
||||
200},
|
||||
{"session and oauth2 expired",
|
||||
&sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))},
|
||||
&oauth2.Token{AccessToken: "mock", Expiry: time.Now().Add(-10 * time.Minute)},
|
||||
identity.MockProvider{RefreshResponse: oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}},
|
||||
mock.Encoder{MarshalResponse: []byte("ok")},
|
||||
200},
|
||||
{"session expired",
|
||||
&sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))},
|
||||
&oauth2.Token{AccessToken: "mock", Expiry: time.Now().Add(10 * time.Minute)},
|
||||
identity.MockProvider{RefreshResponse: oauth2.Token{Expiry: time.Now().Add(10 * time.Minute)}},
|
||||
mock.Encoder{MarshalResponse: []byte("ok")},
|
||||
200},
|
||||
{"failed refresh",
|
||||
&sessions.State{Email: "user@test.example", Expiry: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute))},
|
||||
&oauth2.Token{AccessToken: "mock", Expiry: time.Now().Add(10 * time.Minute)},
|
||||
identity.MockProvider{RefreshError: errors.New("oh no")},
|
||||
mock.Encoder{MarshalResponse: []byte("ok")},
|
||||
302},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
mc := mock_cache.NewMockCacher(ctrl)
|
||||
// just enough is stubbed out here so we can use our own mock provider
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
out := fmt.Sprintf(`{"issuer":"http://%s"}`, r.Host)
|
||||
fmt.Fprintln(w, out)
|
||||
}))
|
||||
defer ts.Close()
|
||||
rURL := ts.URL
|
||||
a, err := New(config.Options{
|
||||
SharedKey: cryptutil.NewBase64Key(),
|
||||
CookieSecret: cryptutil.NewBase64Key(),
|
||||
AuthenticateURL: uriParseHelper("https://authenticate.corp.beyondperimeter.com"),
|
||||
Provider: "oidc",
|
||||
ClientID: "mock",
|
||||
ClientSecret: "mock",
|
||||
ProviderURL: rURL,
|
||||
AuthenticateCallbackPath: "mock",
|
||||
CookieName: "pomerium",
|
||||
Addr: ":0",
|
||||
CacheURL: uriParseHelper("https://authenticate.corp.beyondperimeter.com"),
|
||||
AuthorizeURL: uriParseHelper("https://authorize.corp.beyondperimeter.com"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a.cacheClient = mc
|
||||
a.provider = tt.provider
|
||||
|
||||
u, _ := url.Parse("/oauthGet")
|
||||
params, _ := url.ParseQuery(u.RawQuery)
|
||||
destination := urlutil.NewSignedURL(a.sharedKey,
|
||||
&url.URL{
|
||||
Scheme: "https",
|
||||
Host: "example.com",
|
||||
Path: "/.pomerium/refresh"})
|
||||
|
||||
u.RawQuery = params.Encode()
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, destination.String(), nil)
|
||||
|
||||
jwt, err := a.sharedEncoder.Marshal(tt.session)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rawToken, err := a.encryptedEncoder.Marshal(tt.at)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mc.EXPECT().Get(gomock.Any(), gomock.Any()).Return(rawToken, nil).AnyTimes()
|
||||
mc.EXPECT().Set(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
||||
a.cacheClient = mc
|
||||
|
||||
r.Header.Set("Authorization", fmt.Sprintf("Pomerium %s", jwt))
|
||||
r.Header.Set("Accept", "application/json")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
router := mux.NewRouter()
|
||||
a.Mount(router)
|
||||
router.ServeHTTP(w, r)
|
||||
if status := w.Code; status != tt.wantStatus {
|
||||
t.Errorf("Refresh() error = %v, wantErr %v\n%v", w.Result().StatusCode, tt.wantStatus, w.Body.String())
|
||||
t.Errorf("VerifySession() error = %v, wantErr %v\n%v\n%v", w.Result().StatusCode, tt.wantStatus, w.Header(), w.Body.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -629,3 +508,13 @@ func TestJwksEndpoint(t *testing.T) {
|
|||
expected := `{"keys":[{"use":"sig","kty":"EC","kid":"5b419ade1895fec2d2def6cd33b1b9a018df60db231dc5ecb85cbed6d942813c","crv":"P-256","alg":"ES256","x":"UG5xCP0JTT1H6Iol8jKuTIPVLM04CgW9PlEypNRmWlo","y":"KChF0fR09zm884ymInM29PtSsFdnzExNfLsP-ta1AgQ"}]}`
|
||||
assert.Equal(t, body, expected)
|
||||
}
|
||||
|
||||
type mockDataBrokerServiceClient struct {
|
||||
databroker.DataBrokerServiceClient
|
||||
|
||||
get func(ctx context.Context, in *databroker.GetRequest, opts ...grpc.CallOption) (*databroker.GetResponse, error)
|
||||
}
|
||||
|
||||
func (m mockDataBrokerServiceClient) Get(ctx context.Context, in *databroker.GetRequest, opts ...grpc.CallOption) (*databroker.GetResponse, error) {
|
||||
return m.get(ctx, in, opts...)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue