authenticate: oidc frontchannel-logout endpoint (#1586)

* authenticate: oidc frontchannel-logout endpoint
* move frontchannellogout route and extract logout process
* add frontchannel_logout_uri to wellknown handler
* authenticate: add context to logs in signout process
* docs: single sign-out topic
* gofmt, wording, refactoring method names

Signed-off-by: Bobby DeSimone <bobbydesimone@gmail.com>
Co-authored-by: Bobby DeSimone <bobbydesimone@gmail.com>
This commit is contained in:
Philip Wassermann 2020-12-24 23:30:48 +01:00 committed by GitHub
parent 74db362634
commit 2d3190c74e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 170 additions and 24 deletions

View file

@ -569,7 +569,7 @@ func TestWellKnownEndpoint(t *testing.T) {
rr := httptest.NewRecorder()
h.ServeHTTP(rr, req)
body := rr.Body.String()
expected := "{\"jwks_uri\":\"https://auth.example.com/.well-known/pomerium/jwks.json\",\"authentication_callback_endpoint\":\"https://auth.example.com/oauth2/callback\"}\n"
expected := "{\"jwks_uri\":\"https://auth.example.com/.well-known/pomerium/jwks.json\",\"authentication_callback_endpoint\":\"https://auth.example.com/oauth2/callback\",\"frontchannel_logout_uri\":\"https://auth.example.com/.pomerium/frontchannel-logout\"}\n"
assert.Equal(t, body, expected)
}
@ -673,6 +673,85 @@ func TestAuthenticate_Dashboard(t *testing.T) {
}
}
func TestAuthenticate_FrontchannelLogout(t *testing.T) {
t.Parallel()
tests := []struct {
name string
logoutIssuer string
tokenIssuer string
widthSession bool
sessionStore sessions.SessionStore
provider identity.MockProvider
wantCode int
}{
{"good", "https://idp.pomerium.io", "https://idp.pomerium.io", true, &mstore.Store{}, identity.MockProvider{AuthenticateResponse: oauth2.Token{}}, http.StatusOK},
{"good no session", "https://idp.pomerium.io", "https://idp.pomerium.io", false, &mstore.Store{SaveError: errors.New("error")}, identity.MockProvider{AuthenticateResponse: oauth2.Token{}}, http.StatusOK},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
a := &Authenticate{
state: newAtomicAuthenticateState(&authenticateState{
sessionStore: tt.sessionStore,
encryptedEncoder: mock.Encoder{},
sharedEncoder: mock.Encoder{},
dataBrokerClient: mockDataBrokerServiceClient{
delete: func(ctx context.Context, in *databroker.DeleteRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
return nil, nil
},
get: func(ctx context.Context, in *databroker.GetRequest, opts ...grpc.CallOption) (*databroker.GetResponse, error) {
if !tt.widthSession {
return nil, nil
}
data, err := ptypes.MarshalAny(&session.Session{
Id: "SESSION_ID",
IdToken: &session.IDToken{
Issuer: tt.tokenIssuer,
},
})
if err != nil {
return nil, err
}
return &databroker.GetResponse{
Record: &databroker.Record{
Version: "0001",
Type: data.GetTypeUrl(),
Id: "SESSION_ID",
Data: data,
},
}, nil
},
},
directoryClient: new(mockDirectoryServiceClient),
}),
options: config.NewAtomicOptions(),
provider: identity.NewAtomicAuthenticator(),
}
a.provider.Store(tt.provider)
u, _ := url.Parse("/.pomerium/frontchannel-logout")
params, _ := url.ParseQuery(u.RawQuery)
params.Add("iss", tt.logoutIssuer)
u.RawQuery = params.Encode()
r := httptest.NewRequest(http.MethodGet, u.String(), nil)
w := httptest.NewRecorder()
httputil.HandlerFunc(a.FrontchannelLogout).ServeHTTP(w, r)
if status := w.Code; status != tt.wantCode {
t.Errorf("handler returned wrong status code: got %v want %v", status, tt.wantCode)
}
})
}
}
type mockDataBrokerServiceClient struct {
databroker.DataBrokerServiceClient