diff --git a/client/client.go b/client/client.go index 3f01b1099f0..714b3a606de 100644 --- a/client/client.go +++ b/client/client.go @@ -308,6 +308,11 @@ type Client struct { Lifespans } +type LoginSessionClient struct { + Client + LoginSessionID string `json:"login_session_id,omitempty" db:"login_session_id"` +} + // OAuth 2.0 Client Token Lifespans // // Lifespans of different token types issued for this OAuth 2.0 Client. diff --git a/consent/handler.go b/consent/handler.go index 81dd69b7541..c177754af66 100644 --- a/consent/handler.go +++ b/consent/handler.go @@ -84,12 +84,22 @@ type revokeOAuth2ConsentSessions struct { // in: query Client string `json:"client"` + // If set, deletes only those consent sessions by the Subject that have been granted to the specified session id. Can be combined with client or all parameter. + // + // in: query + LoginSessionId string `json:"login_session_id"` + // Revoke All Consent Sessions // // If set to `true` deletes all consent sessions by the Subject that have been granted. // // in: query All bool `json:"all"` + + // If set to `?trigger_back_channel_logout=true`, performs back channel logout for matching clients + // + // in: query + TriggerBackChannelLogout bool `json:"trigger_back_channel_logout"` } // swagger:route DELETE /admin/oauth2/auth/sessions/consent oAuth2 revokeOAuth2ConsentSessions @@ -113,7 +123,10 @@ type revokeOAuth2ConsentSessions struct { func (h *Handler) revokeOAuth2ConsentSessions(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { subject := r.URL.Query().Get("subject") client := r.URL.Query().Get("client") + loginSessionId := r.URL.Query().Get("login_session_id") + triggerBackChannelLogout := r.URL.Query().Get("trigger_back_channel_logout") allClients := r.URL.Query().Get("all") == "true" + if subject == "" { h.r.Writer().WriteError(w, r, errorsx.WithStack(fosite.ErrInvalidRequest.WithHint(`Query parameter 'subject' is not defined but should have been.`))) return @@ -121,14 +134,41 @@ func (h *Handler) revokeOAuth2ConsentSessions(w http.ResponseWriter, r *http.Req switch { case len(client) > 0: - if err := h.r.ConsentManager().RevokeSubjectClientConsentSession(r.Context(), subject, client); err != nil && !errors.Is(err, x.ErrNotFound) { - h.r.Writer().WriteError(w, r, err) - return + if len(loginSessionId) > 0 { + if triggerBackChannelLogout == "true" { + h.r.ConsentStrategy().ExecuteBackChannelLogoutByClientSession(r.Context(), r, subject, client, loginSessionId) + } + if err := h.r.ConsentManager().RevokeSubjectClientLoginSessionConsentSession(r.Context(), subject, client, loginSessionId); err != nil && !errors.Is(err, x.ErrNotFound) { + h.r.Writer().WriteError(w, r, err) + return + } + } else { + if triggerBackChannelLogout == "true" { + h.r.ConsentStrategy().ExecuteBackChannelLogoutByClient(r.Context(), r, subject, client) + } + if err := h.r.ConsentManager().RevokeSubjectClientConsentSession(r.Context(), subject, client); err != nil && !errors.Is(err, x.ErrNotFound) { + h.r.Writer().WriteError(w, r, err) + return + } } + case allClients: - if err := h.r.ConsentManager().RevokeSubjectConsentSession(r.Context(), subject); err != nil && !errors.Is(err, x.ErrNotFound) { - h.r.Writer().WriteError(w, r, err) - return + if len(loginSessionId) > 0 { + if triggerBackChannelLogout == "true" { + h.r.ConsentStrategy().ExecuteBackChannelLogoutBySession(r.Context(), r, subject, loginSessionId) + } + if err := h.r.ConsentManager().RevokeLoginSessionConsentSession(r.Context(), loginSessionId); err != nil && !errors.Is(err, x.ErrNotFound) { + h.r.Writer().WriteError(w, r, err) + return + } + } else { + if triggerBackChannelLogout == "true" { + h.r.ConsentStrategy().ExecuteBackChannelLogoutBySubject(r.Context(), r, subject) + } + if err := h.r.ConsentManager().RevokeSubjectConsentSession(r.Context(), subject); err != nil && !errors.Is(err, x.ErrNotFound) { + h.r.Writer().WriteError(w, r, err) + return + } } default: h.r.Writer().WriteError(w, r, errorsx.WithStack(fosite.ErrInvalidRequest.WithHint(`Query parameter both 'client' and 'all' is not defined but one of them should have been.`))) diff --git a/consent/handler_test.go b/consent/handler_test.go index 6022674eeb1..f74009e2ea9 100644 --- a/consent/handler_test.go +++ b/consent/handler_test.go @@ -10,9 +10,16 @@ import ( "fmt" "net/http" "net/http/httptest" + "net/url" + "sync" "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/tidwall/gjson" + + "github.com/ory/hydra/driver" + "github.com/ory/x/pointerx" "github.com/ory/hydra/v2/x" @@ -276,3 +283,269 @@ func TestGetLoginRequestWithDuplicateAccept(t *testing.T) { require.Contains(t, result2.RedirectTo, "login_verifier") }) } + +func TestRevokeConsentSession(t *testing.T) { + newWg := func(add int) *sync.WaitGroup { + var wg sync.WaitGroup + wg.Add(add) + return &wg + } + + t.Run("case=subject=subject-1,client=client-1,session=session-1,trigger_back_channel_logout=true", func(t *testing.T) { + conf := internal.NewConfigurationWithDefaults() + reg := internal.NewRegistryMemory(t, conf, &contextx.Default{}) + backChannelWG := newWg(1) + cl := createClientWithBackChannelEndpoint(t, reg, "client-1", []string{"login-session-1"}, backChannelWG) + performLoginFlow(t, reg, "1", cl) + performLoginFlow(t, reg, "2", cl) + performDeleteConsentSession(t, reg, "client-1", "login-session-1", true) + c1, err := reg.ConsentManager().GetConsentRequest(context.Background(), "consent-challenge-1") + require.Error(t, x.ErrNotFound, err) + require.Nil(t, c1) + c2, err := reg.ConsentManager().GetConsentRequest(context.Background(), "consent-challenge-2") + require.NoError(t, err) + require.NotNil(t, c2) + backChannelWG.Wait() + }) + + t.Run("case=subject=subject-1,client=client-1,session=session-1,trigger_back_channel_logout=false", func(t *testing.T) { + conf := internal.NewConfigurationWithDefaults() + reg := internal.NewRegistryMemory(t, conf, &contextx.Default{}) + backChannelWG := newWg(0) + cl := createClientWithBackChannelEndpoint(t, reg, "client-1", []string{}, backChannelWG) + performLoginFlow(t, reg, "1", cl) + performLoginFlow(t, reg, "2", cl) + performDeleteConsentSession(t, reg, "client-1", "login-session-1", false) + c1, err := reg.ConsentManager().GetConsentRequest(context.Background(), "consent-challenge-1") + require.Error(t, x.ErrNotFound, err) + require.Nil(t, c1) + c2, err := reg.ConsentManager().GetConsentRequest(context.Background(), "consent-challenge-2") + require.NoError(t, err) + require.NotNil(t, c2) + backChannelWG.Wait() + }) + + t.Run("case=subject=subject-1,client=client-1,trigger_back_channel_logout=true", func(t *testing.T) { + conf := internal.NewConfigurationWithDefaults() + reg := internal.NewRegistryMemory(t, conf, &contextx.Default{}) + backChannelWG := newWg(2) + cl := createClientWithBackChannelEndpoint(t, reg, "client-1", []string{"login-session-1", "login-session-2"}, backChannelWG) + performLoginFlow(t, reg, "1", cl) + performLoginFlow(t, reg, "2", cl) + + performDeleteConsentSession(t, reg, "client-1", nil, true) + + c1, err := reg.ConsentManager().GetConsentRequest(context.Background(), "consent-challenge-1") + require.Error(t, x.ErrNotFound, err) + require.Nil(t, c1) + c2, err := reg.ConsentManager().GetConsentRequest(context.Background(), "consent-challenge-2") + require.Error(t, x.ErrNotFound, err) + require.Nil(t, c2) + backChannelWG.Wait() + }) + + t.Run("case=subject=subject-1,client=client-1,trigger_back_channel_logout=false", func(t *testing.T) { + conf := internal.NewConfigurationWithDefaults() + reg := internal.NewRegistryMemory(t, conf, &contextx.Default{}) + backChannelWG := newWg(0) + cl := createClientWithBackChannelEndpoint(t, reg, "client-1", []string{}, backChannelWG) + performLoginFlow(t, reg, "1", cl) + performLoginFlow(t, reg, "2", cl) + + performDeleteConsentSession(t, reg, "client-1", nil, false) + + c1, err := reg.ConsentManager().GetConsentRequest(context.Background(), "consent-challenge-1") + require.Error(t, x.ErrNotFound, err) + require.Nil(t, c1) + c2, err := reg.ConsentManager().GetConsentRequest(context.Background(), "consent-challenge-2") + require.Error(t, x.ErrNotFound, err) + require.Nil(t, c2) + backChannelWG.Wait() + }) + + t.Run("case=subject=subject-1,all=true,session=session-1,trigger_back_channel_logout=true", func(t *testing.T) { + conf := internal.NewConfigurationWithDefaults() + reg := internal.NewRegistryMemory(t, conf, &contextx.Default{}) + backChannelWG := newWg(1) + cl1 := createClientWithBackChannelEndpoint(t, reg, "client-1", []string{"login-session-1"}, backChannelWG) + cl2 := createClientWithBackChannelEndpoint(t, reg, "client-2", []string{}, backChannelWG) + performLoginFlow(t, reg, "1", cl1) + performLoginFlow(t, reg, "2", cl2) + + performDeleteConsentSession(t, reg, nil, "login-session-1", true) + + c1, err := reg.ConsentManager().GetConsentRequest(context.Background(), "consent-challenge-1") + require.Error(t, x.ErrNotFound, err) + require.Nil(t, c1) + c2, err := reg.ConsentManager().GetConsentRequest(context.Background(), "consent-challenge-2") + require.NoError(t, err) + require.NotNil(t, c2) + backChannelWG.Wait() + }) + + t.Run("case=subject=subject-1,all=true,session=session-1,trigger_back_channel_logout=false", func(t *testing.T) { + conf := internal.NewConfigurationWithDefaults() + reg := internal.NewRegistryMemory(t, conf, &contextx.Default{}) + backChannelWG := newWg(0) + cl1 := createClientWithBackChannelEndpoint(t, reg, "client-1", []string{}, backChannelWG) + cl2 := createClientWithBackChannelEndpoint(t, reg, "client-2", []string{}, backChannelWG) + performLoginFlow(t, reg, "1", cl1) + performLoginFlow(t, reg, "2", cl2) + + performDeleteConsentSession(t, reg, nil, "login-session-1", false) + + c1, err := reg.ConsentManager().GetConsentRequest(context.Background(), "consent-challenge-1") + require.Error(t, x.ErrNotFound, err) + require.Nil(t, c1) + c2, err := reg.ConsentManager().GetConsentRequest(context.Background(), "consent-challenge-2") + require.NoError(t, err) + require.NotNil(t, c2) + backChannelWG.Wait() + }) + + t.Run("case=subject=subject-1,all=true,trigger_back_channel_logout=true", func(t *testing.T) { + conf := internal.NewConfigurationWithDefaults() + reg := internal.NewRegistryMemory(t, conf, &contextx.Default{}) + backChannelWG := newWg(2) + cl1 := createClientWithBackChannelEndpoint(t, reg, "client-1", []string{"login-session-1"}, backChannelWG) + cl2 := createClientWithBackChannelEndpoint(t, reg, "client-2", []string{"login-session-2"}, backChannelWG) + performLoginFlow(t, reg, "1", cl1) + performLoginFlow(t, reg, "2", cl2) + + performDeleteConsentSession(t, reg, nil, nil, true) + + c1, err := reg.ConsentManager().GetConsentRequest(context.Background(), "consent-challenge-1") + require.Error(t, x.ErrNotFound, err) + require.Nil(t, c1) + c2, err := reg.ConsentManager().GetConsentRequest(context.Background(), "consent-challenge-2") + require.Error(t, x.ErrNotFound, err) + require.Nil(t, c2) + backChannelWG.Wait() + }) + + t.Run("case=subject=subject-1,all=true,trigger_back_channel_logout=false", func(t *testing.T) { + conf := internal.NewConfigurationWithDefaults() + reg := internal.NewRegistryMemory(t, conf, &contextx.Default{}) + backChannelWG := newWg(0) + cl1 := createClientWithBackChannelEndpoint(t, reg, "client-1", []string{}, backChannelWG) + cl2 := createClientWithBackChannelEndpoint(t, reg, "client-2", []string{}, backChannelWG) + performLoginFlow(t, reg, "1", cl1) + performLoginFlow(t, reg, "2", cl2) + + performDeleteConsentSession(t, reg, nil, nil, false) + + c1, err := reg.ConsentManager().GetConsentRequest(context.Background(), "consent-challenge-1") + require.Error(t, x.ErrNotFound, err) + require.Nil(t, c1) + c2, err := reg.ConsentManager().GetConsentRequest(context.Background(), "consent-challenge-2") + require.Error(t, x.ErrNotFound, err) + require.Nil(t, c2) + backChannelWG.Wait() + }) +} + +func performDeleteConsentSession(t *testing.T, reg driver.Registry, client, loginSessionId interface{}, triggerBackChannelLogout bool) { + conf := internal.NewConfigurationWithDefaults() + h := NewHandler(reg, conf) + r := x.NewRouterAdmin(conf.AdminURL) + h.SetRoutes(r) + ts := httptest.NewServer(r) + defer ts.Close() + c := &http.Client{} + + u, _ := url.Parse(ts.URL + "/admin" + SessionsPath + "/consent") + q := u.Query() + q.Set("subject", "subject-1") + if client != nil && len(client.(string)) != 0 { + q.Set("client", client.(string)) + } else { + q.Set("all", "true") + } + if loginSessionId != nil && len(loginSessionId.(string)) != 0 { + q.Set("login_session_id", loginSessionId.(string)) + } + if triggerBackChannelLogout { + q.Set("trigger_back_channel_logout", "true") + } + u.RawQuery = q.Encode() + req, err := http.NewRequest(http.MethodDelete, u.String(), nil) + + require.NoError(t, err) + _, err = c.Do(req) + require.NoError(t, err) +} + +func performLoginFlow(t *testing.T, reg driver.Registry, flowId string, cl *client.Client) { + subject := "subject-1" + loginSessionId := "login-session-" + flowId + loginChallenge := "login-challenge-" + flowId + consentChallenge := "consent-challenge-" + flowId + requestURL := "http://192.0.2.1" + + ls := &LoginSession{ + ID: loginSessionId, + Subject: subject, + } + lr := &LoginRequest{ + ID: loginChallenge, + Subject: subject, + Client: cl, + RequestURL: requestURL, + Verifier: "login-verifier-" + flowId, + SessionID: sqlxx.NullString(loginSessionId), + } + cr := &OAuth2ConsentRequest{ + Client: cl, + ID: consentChallenge, + Verifier: consentChallenge, + CSRF: consentChallenge, + Subject: subject, + LoginChallenge: sqlxx.NullString(loginChallenge), + LoginSessionID: sqlxx.NullString(loginSessionId), + } + hcr := &AcceptOAuth2ConsentRequest{ + ConsentRequest: cr, + ID: consentChallenge, + WasHandled: true, + HandledAt: sqlxx.NullTime(time.Now().UTC()), + } + + require.NoError(t, reg.ConsentManager().CreateLoginSession(context.Background(), ls)) + require.NoError(t, reg.ConsentManager().CreateLoginRequest(context.Background(), lr)) + require.NoError(t, reg.ConsentManager().CreateConsentRequest(context.Background(), cr)) + _, err := reg.ConsentManager().HandleConsentRequest(context.Background(), hcr) + require.NoError(t, err) +} + +func createClientWithBackChannelEndpoint(t *testing.T, reg driver.Registry, clientId string, expectedBackChannelLogoutFlowIds []string, wg *sync.WaitGroup) *client.Client { + return func(t *testing.T, key string, wg *sync.WaitGroup, cb func(t *testing.T, logoutToken gjson.Result)) *client.Client { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer wg.Done() + require.NoError(t, r.ParseForm()) + lt := r.PostFormValue("logout_token") + assert.NotEmpty(t, lt) + token, err := reg.OpenIDJWTStrategy().Decode(r.Context(), lt) + require.NoError(t, err) + var b bytes.Buffer + require.NoError(t, json.NewEncoder(&b).Encode(token.Claims)) + cb(t, gjson.Parse(b.String())) + })) + t.Cleanup(server.Close) + c := &client.Client{ + LegacyClientID: clientId, + BackChannelLogoutURI: server.URL, + } + err := reg.ClientManager().CreateClient(context.Background(), c) + require.NoError(t, err) + return c + }(t, clientId, wg, func(t *testing.T, logoutToken gjson.Result) { + sid := logoutToken.Get("sid").String() + assert.Contains(t, expectedBackChannelLogoutFlowIds, sid) + for i, v := range expectedBackChannelLogoutFlowIds { + if v == sid { + expectedBackChannelLogoutFlowIds = append(expectedBackChannelLogoutFlowIds[:i], expectedBackChannelLogoutFlowIds[i+1:]...) + break + } + } + }) +} diff --git a/consent/manager.go b/consent/manager.go index 2910bcc9e40..1dc77487834 100644 --- a/consent/manager.go +++ b/consent/manager.go @@ -28,7 +28,9 @@ type Manager interface { GetConsentRequest(ctx context.Context, challenge string) (*OAuth2ConsentRequest, error) HandleConsentRequest(ctx context.Context, r *AcceptOAuth2ConsentRequest) (*OAuth2ConsentRequest, error) RevokeSubjectConsentSession(ctx context.Context, user string) error + RevokeLoginSessionConsentSession(ctx context.Context, loginSessionId string) error RevokeSubjectClientConsentSession(ctx context.Context, user, client string) error + RevokeSubjectClientLoginSessionConsentSession(ctx context.Context, user, client, loginSessionId string) error VerifyAndInvalidateConsentRequest(ctx context.Context, verifier string) (*AcceptOAuth2ConsentRequest, error) FindGrantedAndRememberedConsentRequests(ctx context.Context, client, user string) ([]AcceptOAuth2ConsentRequest, error) @@ -51,8 +53,9 @@ type Manager interface { CreateForcedObfuscatedLoginSession(ctx context.Context, session *ForcedObfuscatedLoginSession) error GetForcedObfuscatedLoginSession(ctx context.Context, client, obfuscated string) (*ForcedObfuscatedLoginSession, error) - ListUserAuthenticatedClientsWithFrontChannelLogout(ctx context.Context, subject, sid string) ([]client.Client, error) - ListUserAuthenticatedClientsWithBackChannelLogout(ctx context.Context, subject, sid string) ([]client.Client, error) + ListUserSessionAuthenticatedClientsWithFrontChannelLogout(ctx context.Context, subject, sid string) ([]client.LoginSessionClient, error) + ListUserSessionAuthenticatedClientsWithBackChannelLogout(ctx context.Context, subject, sid string) ([]client.LoginSessionClient, error) + ListUserAuthenticatedClientsWithBackChannelLogout(ctx context.Context, subject string) ([]client.LoginSessionClient, error) CreateLogoutRequest(ctx context.Context, request *LogoutRequest) error GetLogoutRequest(ctx context.Context, challenge string) (*LogoutRequest, error) diff --git a/consent/manager_mock_test.go b/consent/manager_mock_test.go new file mode 100644 index 00000000000..7088bbce2d5 --- /dev/null +++ b/consent/manager_mock_test.go @@ -0,0 +1,765 @@ +// Copyright © 2022 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by MockGen. DO NOT EDIT. +// Source: consent/manager.go +// Package mock_consent is a generated GoMock package. +package consent_test + +import ( + context "context" + reflect "reflect" + time "time" + + "github.com/gobuffalo/pop/v6" + "gopkg.in/square/go-jose.v2" + + "github.com/ory/fosite" + "github.com/ory/hydra/consent" + "github.com/ory/hydra/oauth2/trust" + "github.com/ory/x/popx" + + gomock "github.com/golang/mock/gomock" + + client "github.com/ory/hydra/client" +) + +// MockManager is a mock of Manager interface. +type MockManager struct { + ctrl *gomock.Controller + recorder *MockManagerMockRecorder +} + +// MockManagerMockRecorder is the mock recorder for MockManager. +type MockManagerMockRecorder struct { + mock *MockManager +} + +// NewMockManager creates a new mock instance. +func NewMockManager(ctrl *gomock.Controller) *MockManager { + mock := &MockManager{ctrl: ctrl} + mock.recorder = &MockManagerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockManager) EXPECT() *MockManagerMockRecorder { + return m.recorder +} + +// AcceptLogoutRequest mocks base method. +func (m *MockManager) AcceptLogoutRequest(ctx context.Context, challenge string) (*consent.LogoutRequest, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AcceptLogoutRequest", ctx, challenge) + ret0, _ := ret[0].(*consent.LogoutRequest) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AcceptLogoutRequest indicates an expected call of AcceptLogoutRequest. +func (mr *MockManagerMockRecorder) AcceptLogoutRequest(ctx, challenge interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AcceptLogoutRequest", reflect.TypeOf((*MockManager)(nil).AcceptLogoutRequest), ctx, challenge) +} + +// ConfirmLoginSession mocks base method. +func (m *MockManager) ConfirmLoginSession(ctx context.Context, id string, authTime time.Time, subject string, remember bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ConfirmLoginSession", ctx, id, authTime, subject, remember) + ret0, _ := ret[0].(error) + return ret0 +} + +// ConfirmLoginSession indicates an expected call of ConfirmLoginSession. +func (mr *MockManagerMockRecorder) ConfirmLoginSession(ctx, id, authTime, subject, remember interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConfirmLoginSession", reflect.TypeOf((*MockManager)(nil).ConfirmLoginSession), ctx, id, authTime, subject, remember) +} + +// CountSubjectsGrantedConsentRequests mocks base method. +func (m *MockManager) CountSubjectsGrantedConsentRequests(ctx context.Context, user string) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CountSubjectsGrantedConsentRequests", ctx, user) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CountSubjectsGrantedConsentRequests indicates an expected call of CountSubjectsGrantedConsentRequests. +func (mr *MockManagerMockRecorder) CountSubjectsGrantedConsentRequests(ctx, user interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountSubjectsGrantedConsentRequests", reflect.TypeOf((*MockManager)(nil).CountSubjectsGrantedConsentRequests), ctx, user) +} + +// CreateConsentRequest mocks base method. +func (m *MockManager) CreateConsentRequest(ctx context.Context, req *consent.OAuth2ConsentRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateConsentRequest", ctx, req) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateConsentRequest indicates an expected call of CreateConsentRequest. +func (mr *MockManagerMockRecorder) CreateConsentRequest(ctx, req interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateConsentRequest", reflect.TypeOf((*MockManager)(nil).CreateConsentRequest), ctx, req) +} + +// CreateForcedObfuscatedLoginSession mocks base method. +func (m *MockManager) CreateForcedObfuscatedLoginSession(ctx context.Context, session *consent.ForcedObfuscatedLoginSession) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateForcedObfuscatedLoginSession", ctx, session) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateForcedObfuscatedLoginSession indicates an expected call of CreateForcedObfuscatedLoginSession. +func (mr *MockManagerMockRecorder) CreateForcedObfuscatedLoginSession(ctx, session interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateForcedObfuscatedLoginSession", reflect.TypeOf((*MockManager)(nil).CreateForcedObfuscatedLoginSession), ctx, session) +} + +// CreateLoginRequest mocks base method. +func (m *MockManager) CreateLoginRequest(ctx context.Context, req *consent.LoginRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateLoginRequest", ctx, req) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateLoginRequest indicates an expected call of CreateLoginRequest. +func (mr *MockManagerMockRecorder) CreateLoginRequest(ctx, req interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLoginRequest", reflect.TypeOf((*MockManager)(nil).CreateLoginRequest), ctx, req) +} + +// CreateLoginSession mocks base method. +func (m *MockManager) CreateLoginSession(ctx context.Context, session *consent.LoginSession) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateLoginSession", ctx, session) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateLoginSession indicates an expected call of CreateLoginSession. +func (mr *MockManagerMockRecorder) CreateLoginSession(ctx, session interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLoginSession", reflect.TypeOf((*MockManager)(nil).CreateLoginSession), ctx, session) +} + +// CreateLogoutRequest mocks base method. +func (m *MockManager) CreateLogoutRequest(ctx context.Context, request *consent.LogoutRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateLogoutRequest", ctx, request) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateLogoutRequest indicates an expected call of CreateLogoutRequest. +func (mr *MockManagerMockRecorder) CreateLogoutRequest(ctx, request interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLogoutRequest", reflect.TypeOf((*MockManager)(nil).CreateLogoutRequest), ctx, request) +} + +// DeleteLoginSession mocks base method. +func (m *MockManager) DeleteLoginSession(ctx context.Context, id string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLoginSession", ctx, id) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLoginSession indicates an expected call of DeleteLoginSession. +func (mr *MockManagerMockRecorder) DeleteLoginSession(ctx, id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLoginSession", reflect.TypeOf((*MockManager)(nil).DeleteLoginSession), ctx, id) +} + +// FindGrantedAndRememberedConsentRequests mocks base method. +func (m *MockManager) FindGrantedAndRememberedConsentRequests(ctx context.Context, client, user string) ([]consent.AcceptOAuth2ConsentRequest, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FindGrantedAndRememberedConsentRequests", ctx, client, user) + ret0, _ := ret[0].([]consent.AcceptOAuth2ConsentRequest) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FindGrantedAndRememberedConsentRequests indicates an expected call of FindGrantedAndRememberedConsentRequests. +func (mr *MockManagerMockRecorder) FindGrantedAndRememberedConsentRequests(ctx, client, user interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindGrantedAndRememberedConsentRequests", reflect.TypeOf((*MockManager)(nil).FindGrantedAndRememberedConsentRequests), ctx, client, user) +} + +// FindSubjectsGrantedConsentRequests mocks base method. +func (m *MockManager) FindSubjectsGrantedConsentRequests(ctx context.Context, user string, limit, offset int) ([]consent.AcceptOAuth2ConsentRequest, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FindSubjectsGrantedConsentRequests", ctx, user, limit, offset) + ret0, _ := ret[0].([]consent.AcceptOAuth2ConsentRequest) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FindSubjectsGrantedConsentRequests indicates an expected call of FindSubjectsGrantedConsentRequests. +func (mr *MockManagerMockRecorder) FindSubjectsGrantedConsentRequests(ctx, user, limit, offset interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindSubjectsGrantedConsentRequests", reflect.TypeOf((*MockManager)(nil).FindSubjectsGrantedConsentRequests), ctx, user, limit, offset) +} + +// GetConsentRequest mocks base method. +func (m *MockManager) GetConsentRequest(ctx context.Context, challenge string) (*consent.OAuth2ConsentRequest, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetConsentRequest", ctx, challenge) + ret0, _ := ret[0].(*consent.OAuth2ConsentRequest) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetConsentRequest indicates an expected call of GetConsentRequest. +func (mr *MockManagerMockRecorder) GetConsentRequest(ctx, challenge interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConsentRequest", reflect.TypeOf((*MockManager)(nil).GetConsentRequest), ctx, challenge) +} + +// GetForcedObfuscatedLoginSession mocks base method. +func (m *MockManager) GetForcedObfuscatedLoginSession(ctx context.Context, client, obfuscated string) (*consent.ForcedObfuscatedLoginSession, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetForcedObfuscatedLoginSession", ctx, client, obfuscated) + ret0, _ := ret[0].(*consent.ForcedObfuscatedLoginSession) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetForcedObfuscatedLoginSession indicates an expected call of GetForcedObfuscatedLoginSession. +func (mr *MockManagerMockRecorder) GetForcedObfuscatedLoginSession(ctx, client, obfuscated interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetForcedObfuscatedLoginSession", reflect.TypeOf((*MockManager)(nil).GetForcedObfuscatedLoginSession), ctx, client, obfuscated) +} + +// GetLoginRequest mocks base method. +func (m *MockManager) GetLoginRequest(ctx context.Context, challenge string) (*consent.LoginRequest, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLoginRequest", ctx, challenge) + ret0, _ := ret[0].(*consent.LoginRequest) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLoginRequest indicates an expected call of GetLoginRequest. +func (mr *MockManagerMockRecorder) GetLoginRequest(ctx, challenge interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLoginRequest", reflect.TypeOf((*MockManager)(nil).GetLoginRequest), ctx, challenge) +} + +// GetLogoutRequest mocks base method. +func (m *MockManager) GetLogoutRequest(ctx context.Context, challenge string) (*consent.LogoutRequest, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLogoutRequest", ctx, challenge) + ret0, _ := ret[0].(*consent.LogoutRequest) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLogoutRequest indicates an expected call of GetLogoutRequest. +func (mr *MockManagerMockRecorder) GetLogoutRequest(ctx, challenge interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogoutRequest", reflect.TypeOf((*MockManager)(nil).GetLogoutRequest), ctx, challenge) +} + +// GetRememberedLoginSession mocks base method. +func (m *MockManager) GetRememberedLoginSession(ctx context.Context, id string) (*consent.LoginSession, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRememberedLoginSession", ctx, id) + ret0, _ := ret[0].(*consent.LoginSession) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRememberedLoginSession indicates an expected call of GetRememberedLoginSession. +func (mr *MockManagerMockRecorder) GetRememberedLoginSession(ctx, id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRememberedLoginSession", reflect.TypeOf((*MockManager)(nil).GetRememberedLoginSession), ctx, id) +} + +// HandleConsentRequest mocks base method. +func (m *MockManager) HandleConsentRequest(ctx context.Context, challenge string, r *consent.AcceptOAuth2ConsentRequest) (*consent.OAuth2ConsentRequest, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HandleConsentRequest", ctx, challenge, r) + ret0, _ := ret[0].(*consent.OAuth2ConsentRequest) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HandleConsentRequest indicates an expected call of HandleConsentRequest. +func (mr *MockManagerMockRecorder) HandleConsentRequest(ctx, challenge, r interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleConsentRequest", reflect.TypeOf((*MockManager)(nil).HandleConsentRequest), ctx, challenge, r) +} + +// HandleLoginRequest mocks base method. +func (m *MockManager) HandleLoginRequest(ctx context.Context, challenge string, r *consent.HandledLoginRequest) (*consent.LoginRequest, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HandleLoginRequest", ctx, challenge, r) + ret0, _ := ret[0].(*consent.LoginRequest) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HandleLoginRequest indicates an expected call of HandleLoginRequest. +func (mr *MockManagerMockRecorder) HandleLoginRequest(ctx, challenge, r interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleLoginRequest", reflect.TypeOf((*MockManager)(nil).HandleLoginRequest), ctx, challenge, r) +} + +// ListUserAuthenticatedClientsWithBackChannelLogout mocks base method. +func (m *MockManager) ListUserAuthenticatedClientsWithBackChannelLogout(ctx context.Context, subject string) ([]client.LoginSessionClient, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListUserAuthenticatedClientsWithBackChannelLogout", ctx, subject) + ret0, _ := ret[0].([]client.LoginSessionClient) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListUserAuthenticatedClientsWithBackChannelLogout indicates an expected call of ListUserAuthenticatedClientsWithBackChannelLogout. +func (mr *MockManagerMockRecorder) ListUserAuthenticatedClientsWithBackChannelLogout(ctx, subject interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListUserAuthenticatedClientsWithBackChannelLogout", reflect.TypeOf((*MockManager)(nil).ListUserAuthenticatedClientsWithBackChannelLogout), ctx, subject) +} + +// ListUserSessionAuthenticatedClientsWithBackChannelLogout mocks base method. +func (m *MockManager) ListUserSessionAuthenticatedClientsWithBackChannelLogout(ctx context.Context, subject, sid string) ([]client.LoginSessionClient, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListUserSessionAuthenticatedClientsWithBackChannelLogout", ctx, subject, sid) + ret0, _ := ret[0].([]client.LoginSessionClient) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListUserSessionAuthenticatedClientsWithBackChannelLogout indicates an expected call of ListUserSessionAuthenticatedClientsWithBackChannelLogout. +func (mr *MockManagerMockRecorder) ListUserSessionAuthenticatedClientsWithBackChannelLogout(ctx, subject, sid interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListUserSessionAuthenticatedClientsWithBackChannelLogout", reflect.TypeOf((*MockManager)(nil).ListUserSessionAuthenticatedClientsWithBackChannelLogout), ctx, subject, sid) +} + +// ListUserSessionAuthenticatedClientsWithFrontChannelLogout mocks base method. +func (m *MockManager) ListUserSessionAuthenticatedClientsWithFrontChannelLogout(ctx context.Context, subject, sid string) ([]client.LoginSessionClient, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListUserSessionAuthenticatedClientsWithFrontChannelLogout", ctx, subject, sid) + ret0, _ := ret[0].([]client.LoginSessionClient) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListUserSessionAuthenticatedClientsWithFrontChannelLogout indicates an expected call of ListUserSessionAuthenticatedClientsWithFrontChannelLogout. +func (mr *MockManagerMockRecorder) ListUserSessionAuthenticatedClientsWithFrontChannelLogout(ctx, subject, sid interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListUserSessionAuthenticatedClientsWithFrontChannelLogout", reflect.TypeOf((*MockManager)(nil).ListUserSessionAuthenticatedClientsWithFrontChannelLogout), ctx, subject, sid) +} + +// RejectLogoutRequest mocks base method. +func (m *MockManager) RejectLogoutRequest(ctx context.Context, challenge string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RejectLogoutRequest", ctx, challenge) + ret0, _ := ret[0].(error) + return ret0 +} + +// RejectLogoutRequest indicates an expected call of RejectLogoutRequest. +func (mr *MockManagerMockRecorder) RejectLogoutRequest(ctx, challenge interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RejectLogoutRequest", reflect.TypeOf((*MockManager)(nil).RejectLogoutRequest), ctx, challenge) +} + +// RevokeLoginSessionConsentSession mocks base method. +func (m *MockManager) RevokeLoginSessionConsentSession(ctx context.Context, loginSessionId string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RevokeLoginSessionConsentSession", ctx, loginSessionId) + ret0, _ := ret[0].(error) + return ret0 +} + +// RevokeLoginSessionConsentSession indicates an expected call of RevokeLoginSessionConsentSession. +func (mr *MockManagerMockRecorder) RevokeLoginSessionConsentSession(ctx, loginSessionId interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevokeLoginSessionConsentSession", reflect.TypeOf((*MockManager)(nil).RevokeLoginSessionConsentSession), ctx, loginSessionId) +} + +// RevokeSubjectClientConsentSession mocks base method. +func (m *MockManager) RevokeSubjectClientConsentSession(ctx context.Context, user, client string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RevokeSubjectClientConsentSession", ctx, user, client) + ret0, _ := ret[0].(error) + return ret0 +} + +// RevokeSubjectClientConsentSession indicates an expected call of RevokeSubjectClientConsentSession. +func (mr *MockManagerMockRecorder) RevokeSubjectClientConsentSession(ctx, user, client interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevokeSubjectClientConsentSession", reflect.TypeOf((*MockManager)(nil).RevokeSubjectClientConsentSession), ctx, user, client) +} + +// RevokeSubjectClientLoginSessionConsentSession mocks base method. +func (m *MockManager) RevokeSubjectClientLoginSessionConsentSession(ctx context.Context, user, client, loginSessionId string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RevokeSubjectClientLoginSessionConsentSession", ctx, user, client, loginSessionId) + ret0, _ := ret[0].(error) + return ret0 +} + +// RevokeSubjectClientLoginSessionConsentSession indicates an expected call of RevokeSubjectClientLoginSessionConsentSession. +func (mr *MockManagerMockRecorder) RevokeSubjectClientLoginSessionConsentSession(ctx, user, client, loginSessionId interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevokeSubjectClientLoginSessionConsentSession", reflect.TypeOf((*MockManager)(nil).RevokeSubjectClientLoginSessionConsentSession), ctx, user, client, loginSessionId) +} + +// RevokeSubjectConsentSession mocks base method. +func (m *MockManager) RevokeSubjectConsentSession(ctx context.Context, user string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RevokeSubjectConsentSession", ctx, user) + ret0, _ := ret[0].(error) + return ret0 +} + +// RevokeSubjectConsentSession indicates an expected call of RevokeSubjectConsentSession. +func (mr *MockManagerMockRecorder) RevokeSubjectConsentSession(ctx, user interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevokeSubjectConsentSession", reflect.TypeOf((*MockManager)(nil).RevokeSubjectConsentSession), ctx, user) +} + +// RevokeSubjectLoginSession mocks base method. +func (m *MockManager) RevokeSubjectLoginSession(ctx context.Context, user string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RevokeSubjectLoginSession", ctx, user) + ret0, _ := ret[0].(error) + return ret0 +} + +// RevokeSubjectLoginSession indicates an expected call of RevokeSubjectLoginSession. +func (mr *MockManagerMockRecorder) RevokeSubjectLoginSession(ctx, user interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevokeSubjectLoginSession", reflect.TypeOf((*MockManager)(nil).RevokeSubjectLoginSession), ctx, user) +} + +// VerifyAndInvalidateConsentRequest mocks base method. +func (m *MockManager) VerifyAndInvalidateConsentRequest(ctx context.Context, verifier string) (*consent.AcceptOAuth2ConsentRequest, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifyAndInvalidateConsentRequest", ctx, verifier) + ret0, _ := ret[0].(*consent.AcceptOAuth2ConsentRequest) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// VerifyAndInvalidateConsentRequest indicates an expected call of VerifyAndInvalidateConsentRequest. +func (mr *MockManagerMockRecorder) VerifyAndInvalidateConsentRequest(ctx, verifier interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyAndInvalidateConsentRequest", reflect.TypeOf((*MockManager)(nil).VerifyAndInvalidateConsentRequest), ctx, verifier) +} + +// VerifyAndInvalidateLoginRequest mocks base method. +func (m *MockManager) VerifyAndInvalidateLoginRequest(ctx context.Context, verifier string) (*consent.HandledLoginRequest, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifyAndInvalidateLoginRequest", ctx, verifier) + ret0, _ := ret[0].(*consent.HandledLoginRequest) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// VerifyAndInvalidateLoginRequest indicates an expected call of VerifyAndInvalidateLoginRequest. +func (mr *MockManagerMockRecorder) VerifyAndInvalidateLoginRequest(ctx, verifier interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyAndInvalidateLoginRequest", reflect.TypeOf((*MockManager)(nil).VerifyAndInvalidateLoginRequest), ctx, verifier) +} + +// VerifyAndInvalidateLogoutRequest mocks base method. +func (m *MockManager) VerifyAndInvalidateLogoutRequest(ctx context.Context, verifier string) (*consent.LogoutRequest, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifyAndInvalidateLogoutRequest", ctx, verifier) + ret0, _ := ret[0].(*consent.LogoutRequest) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// VerifyAndInvalidateLogoutRequest indicates an expected call of VerifyAndInvalidateLogoutRequest. +func (mr *MockManagerMockRecorder) VerifyAndInvalidateLogoutRequest(ctx, verifier interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyAndInvalidateLogoutRequest", reflect.TypeOf((*MockManager)(nil).VerifyAndInvalidateLogoutRequest), ctx, verifier) +} + +func (m *MockManager) CreateClient(ctx context.Context, c *client.Client) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) UpdateClient(ctx context.Context, c *client.Client) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) DeleteClient(ctx context.Context, id string) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) GetClients(ctx context.Context, filters client.Filter) ([]client.Client, error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) CountClients(ctx context.Context) (int, error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) GetConcreteClient(ctx context.Context, id string) (*client.Client, error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) Authenticate(ctx context.Context, id string, secret []byte) (*client.Client, error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) GetClient(ctx context.Context, id string) (fosite.Client, error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) ClientAssertionJWTValid(ctx context.Context, jti string) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) SetClientAssertionJWT(ctx context.Context, jti string, exp time.Time) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) CreateAuthorizeCodeSession(ctx context.Context, code string, request fosite.Requester) (err error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) GetAuthorizeCodeSession(ctx context.Context, code string, session fosite.Session) (request fosite.Requester, err error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) InvalidateAuthorizeCodeSession(ctx context.Context, code string) (err error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) CreateAccessTokenSession(ctx context.Context, signature string, request fosite.Requester) (err error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) GetAccessTokenSession(ctx context.Context, signature string, session fosite.Session) (request fosite.Requester, err error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) DeleteAccessTokenSession(ctx context.Context, signature string) (err error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) CreateRefreshTokenSession(ctx context.Context, signature string, request fosite.Requester) (err error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) GetRefreshTokenSession(ctx context.Context, signature string, session fosite.Session) (request fosite.Requester, err error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) DeleteRefreshTokenSession(ctx context.Context, signature string) (err error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) CreateOpenIDConnectSession(ctx context.Context, authorizeCode string, requester fosite.Requester) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) GetOpenIDConnectSession(ctx context.Context, authorizeCode string, requester fosite.Requester) (fosite.Requester, error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) GetPKCERequestSession(ctx context.Context, signature string, session fosite.Session) (fosite.Requester, error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) CreatePKCERequestSession(ctx context.Context, signature string, requester fosite.Requester) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) DeletePKCERequestSession(ctx context.Context, signature string) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) GetPublicKey(ctx context.Context, issuer string, subject string, keyId string) (*jose.JSONWebKey, error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) GetPublicKeys(ctx context.Context, issuer string, subject string) (*jose.JSONWebKeySet, error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) GetPublicKeyScopes(ctx context.Context, issuer string, subject string, keyId string) ([]string, error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) IsJWTUsed(ctx context.Context, jti string) (bool, error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) MarkJWTUsedForTime(ctx context.Context, jti string, exp time.Time) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) RevokeRefreshToken(ctx context.Context, requestID string) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) RevokeAccessToken(ctx context.Context, requestID string) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) FlushInactiveAccessTokens(ctx context.Context, notAfter time.Time, limit int, batchSize int) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) FlushInactiveLoginConsentRequests(ctx context.Context, notAfter time.Time, limit int, batchSize int) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) DeleteAccessTokens(ctx context.Context, clientID string) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) FlushInactiveRefreshTokens(ctx context.Context, notAfter time.Time, limit int, batchSize int) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) DeleteOpenIDConnectSession(ctx context.Context, authorizeCode string) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) GenerateAndPersistKeySet(ctx context.Context, set, kid, alg, use string) (*jose.JSONWebKeySet, error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) AddKey(ctx context.Context, set string, key *jose.JSONWebKey) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) AddKeySet(ctx context.Context, set string, keys *jose.JSONWebKeySet) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) UpdateKey(ctx context.Context, set string, key *jose.JSONWebKey) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) UpdateKeySet(ctx context.Context, set string, keys *jose.JSONWebKeySet) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) GetKey(ctx context.Context, set, kid string) (*jose.JSONWebKeySet, error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) GetKeySet(ctx context.Context, set string) (*jose.JSONWebKeySet, error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) DeleteKey(ctx context.Context, set, kid string) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) DeleteKeySet(ctx context.Context, set string) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) CreateGrant(ctx context.Context, g trust.Grant, publicKey jose.JSONWebKey) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) GetConcreteGrant(ctx context.Context, id string) (trust.Grant, error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) DeleteGrant(ctx context.Context, id string) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) GetGrants(ctx context.Context, limit, offset int, optionalIssuer string) ([]trust.Grant, error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) CountGrants(ctx context.Context) (int, error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) FlushInactiveGrants(ctx context.Context, notAfter time.Time, limit int, batchSize int) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) MigrationStatus(ctx context.Context) (popx.MigrationStatuses, error) { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) MigrateDown(ctx context.Context, i int) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) MigrateUp(ctx context.Context) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) PrepareMigration(ctx context.Context) error { + //TODO implement me + panic("implement me") +} + +func (m *MockManager) Connection(ctx context.Context) *pop.Connection { + //TODO implement me + panic("implement me") +} diff --git a/consent/manager_test_helpers.go b/consent/manager_test_helpers.go index 084b9d4c4a4..d905d9985b5 100644 --- a/consent/manager_test_helpers.go +++ b/consent/manager_test_helpers.go @@ -839,7 +839,7 @@ func ManagerTests(m Manager, clientManager client.Manager, fositeManager x.Fosit } for _, ls := range sessions { - check := func(t *testing.T, expected map[string][]client.Client, actual []client.Client) { + check := func(t *testing.T, expected map[string][]client.Client, actual []client.LoginSessionClient) { es, ok := expected[ls.ID] if !ok { require.Len(t, actual, 0) @@ -861,14 +861,14 @@ func ManagerTests(m Manager, clientManager client.Manager, fositeManager x.Fosit } } - t.Run(fmt.Sprintf("method=ListUserAuthenticatedClientsWithFrontChannelLogout/session=%s/subject=%s", ls.ID, ls.Subject), func(t *testing.T) { - actual, err := m.ListUserAuthenticatedClientsWithFrontChannelLogout(context.Background(), ls.Subject, ls.ID) + t.Run(fmt.Sprintf("method=ListUserSessionAuthenticatedClientsWithFrontChannelLogout/session=%s/subject=%s", ls.ID, ls.Subject), func(t *testing.T) { + actual, err := m.ListUserSessionAuthenticatedClientsWithFrontChannelLogout(context.Background(), ls.Subject, ls.ID) require.NoError(t, err) check(t, frontChannels, actual) }) - t.Run(fmt.Sprintf("method=ListUserAuthenticatedClientsWithBackChannelLogout/session=%s", ls.ID), func(t *testing.T) { - actual, err := m.ListUserAuthenticatedClientsWithBackChannelLogout(context.Background(), ls.Subject, ls.ID) + t.Run(fmt.Sprintf("method=ListUserSessionAuthenticatedClientsWithBackChannelLogout/session=%s", ls.ID), func(t *testing.T) { + actual, err := m.ListUserSessionAuthenticatedClientsWithBackChannelLogout(context.Background(), ls.Subject, ls.ID) require.NoError(t, err) check(t, backChannels, actual) }) diff --git a/consent/strategy.go b/consent/strategy.go index 9d31b3de4b1..8f9885985d3 100644 --- a/consent/strategy.go +++ b/consent/strategy.go @@ -17,4 +17,8 @@ type Strategy interface { HandleOpenIDConnectLogout(ctx context.Context, w http.ResponseWriter, r *http.Request) (*LogoutResult, error) HandleHeadlessLogout(ctx context.Context, w http.ResponseWriter, r *http.Request, sid string) error ObfuscateSubjectIdentifier(ctx context.Context, cl fosite.Client, subject, forcedIdentifier string) (string, error) + ExecuteBackChannelLogoutBySession(ctx context.Context, r *http.Request, subject, sid string) + ExecuteBackChannelLogoutBySubject(ctx context.Context, r *http.Request, subject string) + ExecuteBackChannelLogoutByClient(ctx context.Context, r *http.Request, subject, client string) + ExecuteBackChannelLogoutByClientSession(ctx context.Context, r *http.Request, subject, client, sid string) } diff --git a/consent/strategy_default.go b/consent/strategy_default.go index 0de9ac2b168..bca3e9f41ac 100644 --- a/consent/strategy_default.go +++ b/consent/strategy_default.go @@ -619,7 +619,7 @@ func (s *DefaultStrategy) verifyConsent(ctx context.Context, w http.ResponseWrit } func (s *DefaultStrategy) generateFrontChannelLogoutURLs(ctx context.Context, subject, sid string) ([]string, error) { - clients, err := s.r.ConsentManager().ListUserAuthenticatedClientsWithFrontChannelLogout(ctx, subject, sid) + clients, err := s.r.ConsentManager().ListUserSessionAuthenticatedClientsWithFrontChannelLogout(ctx, subject, sid) if err != nil { return nil, err } @@ -641,11 +641,15 @@ func (s *DefaultStrategy) generateFrontChannelLogoutURLs(ctx context.Context, su } func (s *DefaultStrategy) executeBackChannelLogout(ctx context.Context, r *http.Request, subject, sid string) error { - clients, err := s.r.ConsentManager().ListUserAuthenticatedClientsWithBackChannelLogout(ctx, subject, sid) + clients, err := s.r.ConsentManager().ListUserSessionAuthenticatedClientsWithBackChannelLogout(ctx, subject, sid) if err != nil { return err } + return s.executeBackChannelLogoutForClients(ctx, r, clients) +} + +func (s *DefaultStrategy) executeBackChannelLogoutForClients(ctx context.Context, r *http.Request, clients []client.LoginSessionClient) error { openIDKeyID, err := s.r.OpenIDJWTStrategy().GetPublicKeyID(ctx) if err != nil { return err @@ -672,7 +676,7 @@ func (s *DefaultStrategy) executeBackChannelLogout(ctx context.Context, r *http. "iat": time.Now().UTC().Unix(), "jti": uuid.New(), "events": map[string]struct{}{"http://schemas.openid.net/event/backchannel-logout": {}}, - "sid": sid, + "sid": c.LoginSessionID, }, &jwt.Headers{ Extra: map[string]interface{}{"kid": openIDKeyID}, }) @@ -1057,3 +1061,38 @@ func (s *DefaultStrategy) ObfuscateSubjectIdentifier(ctx context.Context, cl fos } return subject, nil } + +func (s *DefaultStrategy) ExecuteBackChannelLogoutBySession(ctx context.Context, r *http.Request, subject, sid string) { + _ = s.executeBackChannelLogout(ctx, r, subject, sid) +} + +func (s *DefaultStrategy) ExecuteBackChannelLogoutBySubject(ctx context.Context, r *http.Request, subject string) { + clients, err := s.r.ConsentManager().ListUserAuthenticatedClientsWithBackChannelLogout(ctx, subject) + if err == nil { + _ = s.executeBackChannelLogoutForClients(ctx, r, clients) + } +} + +func (s *DefaultStrategy) ExecuteBackChannelLogoutByClient(ctx context.Context, r *http.Request, subject, client string) { + clients, err := s.r.ConsentManager().ListUserAuthenticatedClientsWithBackChannelLogout(ctx, subject) + if err == nil { + for i := len(clients) - 1; i >= 0; i-- { + if clients[i].LegacyClientID != client { + clients = append(clients[:i], clients[i+1:]...) + } + } + _ = s.executeBackChannelLogoutForClients(ctx, r, clients) + } +} + +func (s *DefaultStrategy) ExecuteBackChannelLogoutByClientSession(ctx context.Context, r *http.Request, subject, client, sid string) { + clients, err := s.r.ConsentManager().ListUserSessionAuthenticatedClientsWithBackChannelLogout(ctx, subject, sid) + if err == nil { + for i := len(clients) - 1; i >= 0; i-- { + if clients[i].LegacyClientID != client { + clients = append(clients[:i], clients[i+1:]...) + } + } + _ = s.executeBackChannelLogoutForClients(ctx, r, clients) + } +} diff --git a/internal/httpclient/api/openapi.yaml b/internal/httpclient/api/openapi.yaml index bdc7f0ede0b..a27eee61327 100644 --- a/internal/httpclient/api/openapi.yaml +++ b/internal/httpclient/api/openapi.yaml @@ -1041,6 +1041,16 @@ paths: schema: type: string style: form + - description: "If set, deletes only those consent sessions by the Subject that\ + \ have been granted to the specified session id. Can be combined with client\ + \ or all parameter." + explode: true + in: query + name: login_session_id + required: false + schema: + type: string + style: form - description: |- Revoke All Consent Sessions @@ -1052,6 +1062,15 @@ paths: schema: type: boolean style: form + - description: "If set to `?trigger_back_channel_logout=true`, performs back\ + \ channel logout for matching clients" + explode: true + in: query + name: trigger_back_channel_logout + required: false + schema: + type: boolean + style: form responses: "204": description: "Empty responses are sent when, for example, resources are\ diff --git a/internal/httpclient/docs/OAuth2Api.md b/internal/httpclient/docs/OAuth2Api.md index c5b4aff638c..ee8731b96d1 100644 --- a/internal/httpclient/docs/OAuth2Api.md +++ b/internal/httpclient/docs/OAuth2Api.md @@ -1532,7 +1532,7 @@ No authorization required ## RevokeOAuth2ConsentSessions -> RevokeOAuth2ConsentSessions(ctx).Subject(subject).Client(client).All(all).Execute() +> RevokeOAuth2ConsentSessions(ctx).Subject(subject).Client(client).LoginSessionId(loginSessionId).All(all).TriggerBackChannelLogout(triggerBackChannelLogout).Execute() Revoke OAuth 2.0 Consent Sessions of a Subject @@ -1553,11 +1553,13 @@ import ( func main() { subject := "subject_example" // string | OAuth 2.0 Consent Subject The subject whose consent sessions should be deleted. client := "client_example" // string | OAuth 2.0 Client ID If set, deletes only those consent sessions that have been granted to the specified OAuth 2.0 Client ID. (optional) + loginSessionId := "loginSessionId_example" // string | If set, deletes only those consent sessions by the Subject that have been granted to the specified session id. Can be combined with client or all parameter. (optional) all := true // bool | Revoke All Consent Sessions If set to `true` deletes all consent sessions by the Subject that have been granted. (optional) + triggerBackChannelLogout := true // bool | If set to `?trigger_back_channel_logout=true`, performs back channel logout for matching clients (optional) configuration := openapiclient.NewConfiguration() apiClient := openapiclient.NewAPIClient(configuration) - resp, r, err := apiClient.OAuth2Api.RevokeOAuth2ConsentSessions(context.Background()).Subject(subject).Client(client).All(all).Execute() + resp, r, err := apiClient.OAuth2Api.RevokeOAuth2ConsentSessions(context.Background()).Subject(subject).Client(client).LoginSessionId(loginSessionId).All(all).TriggerBackChannelLogout(triggerBackChannelLogout).Execute() if err != nil { fmt.Fprintf(os.Stderr, "Error when calling `OAuth2Api.RevokeOAuth2ConsentSessions``: %v\n", err) fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) @@ -1578,7 +1580,9 @@ Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **subject** | **string** | OAuth 2.0 Consent Subject The subject whose consent sessions should be deleted. | **client** | **string** | OAuth 2.0 Client ID If set, deletes only those consent sessions that have been granted to the specified OAuth 2.0 Client ID. | + **loginSessionId** | **string** | If set, deletes only those consent sessions by the Subject that have been granted to the specified session id. Can be combined with client or all parameter. | **all** | **bool** | Revoke All Consent Sessions If set to `true` deletes all consent sessions by the Subject that have been granted. | + **triggerBackChannelLogout** | **bool** | If set to `?trigger_back_channel_logout=true`, performs back channel logout for matching clients | ### Return type diff --git a/oauth2/oauth2_helper_test.go b/oauth2/oauth2_helper_test.go index ea679c24189..8100d99429d 100644 --- a/oauth2/oauth2_helper_test.go +++ b/oauth2/oauth2_helper_test.go @@ -25,6 +25,22 @@ type consentMock struct { requestTime time.Time } +func (c *consentMock) ExecuteBackChannelLogoutBySession(ctx context.Context, r *http.Request, subject, sid string) { + panic("not implemented") +} + +func (c *consentMock) ExecuteBackChannelLogoutBySubject(ctx context.Context, r *http.Request, subject string) { + panic("not implemented") +} + +func (c *consentMock) ExecuteBackChannelLogoutByClient(ctx context.Context, r *http.Request, subject, client string) { + panic("not implemented") +} + +func (c *consentMock) ExecuteBackChannelLogoutByClientSession(ctx context.Context, r *http.Request, subject, client, sid string) { + panic("not implemented") +} + func (c *consentMock) HandleOAuth2AuthorizationRequest(ctx context.Context, w http.ResponseWriter, r *http.Request, req fosite.AuthorizeRequester) (*consent.AcceptOAuth2ConsentRequest, error) { if c.deny { return nil, fosite.ErrRequestForbidden diff --git a/persistence/sql/persister_consent.go b/persistence/sql/persister_consent.go index 8f1fca3d490..af1a8c22def 100644 --- a/persistence/sql/persister_consent.go +++ b/persistence/sql/persister_consent.go @@ -35,6 +35,13 @@ func (p *Persister) RevokeSubjectConsentSession(ctx context.Context, user string return p.transaction(ctx, p.revokeConsentSession("consent_challenge_id IS NOT NULL AND subject = ?", user)) } +func (p *Persister) RevokeLoginSessionConsentSession(ctx context.Context, loginSessionId string) error { + ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.RevokeLoginSessionConsentSession") + defer span.End() + + return p.transaction(ctx, p.revokeConsentSession("consent_challenge_id IS NOT NULL AND login_session_id = ?", loginSessionId)) +} + func (p *Persister) RevokeSubjectClientConsentSession(ctx context.Context, user, client string) error { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.RevokeSubjectClientConsentSession") defer span.End() @@ -42,6 +49,13 @@ func (p *Persister) RevokeSubjectClientConsentSession(ctx context.Context, user, return p.transaction(ctx, p.revokeConsentSession("consent_challenge_id IS NOT NULL AND subject = ? AND client_id = ?", user, client)) } +func (p *Persister) RevokeSubjectClientLoginSessionConsentSession(ctx context.Context, user, client, loginSessionId string) error { + ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.RevokeSubjectClientLoginSessionConsentSession") + defer span.End() + + return p.transaction(ctx, p.revokeConsentSession("consent_challenge_id IS NOT NULL AND subject = ? AND client_id = ? AND login_session_id = ?", user, client, loginSessionId)) +} + func (p *Persister) revokeConsentSession(whereStmt string, whereArgs ...interface{}) func(context.Context, *pop.Connection) error { return func(ctx context.Context, c *pop.Connection) error { fs := make([]*flow.Flow, 0) @@ -535,29 +549,35 @@ func (p *Persister) filterExpiredConsentRequests(ctx context.Context, requests [ return result, nil } -func (p *Persister) ListUserAuthenticatedClientsWithFrontChannelLogout(ctx context.Context, subject, sid string) ([]client.Client, error) { - ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.ListUserAuthenticatedClientsWithFrontChannelLogout") +func (p *Persister) ListUserSessionAuthenticatedClientsWithFrontChannelLogout(ctx context.Context, subject, sid string) ([]client.LoginSessionClient, error) { + ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.ListUserSessionAuthenticatedClientsWithFrontChannelLogout") defer span.End() - return p.listUserAuthenticatedClients(ctx, subject, sid, "front") + return p.listUserSessionAuthenticatedClients(ctx, subject, sid, "front") +} + +func (p *Persister) ListUserSessionAuthenticatedClientsWithBackChannelLogout(ctx context.Context, subject, sid string) ([]client.LoginSessionClient, error) { + ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.ListUserSessionAuthenticatedClientsWithBackChannelLogout") + defer span.End() + return p.listUserSessionAuthenticatedClients(ctx, subject, sid, "back") } -func (p *Persister) ListUserAuthenticatedClientsWithBackChannelLogout(ctx context.Context, subject, sid string) ([]client.Client, error) { - ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.ListUserAuthenticatedClientsWithBackChannelLogout") +func (p *Persister) ListUserAuthenticatedClientsWithBackChannelLogout(ctx context.Context, subject string) ([]client.LoginSessionClient, error) { + ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.ListUserSessionAuthenticatedClientsWithBackChannelLogout") defer span.End() - return p.listUserAuthenticatedClients(ctx, subject, sid, "back") + return p.listUserAuthenticatedClients(ctx, subject, "back") } -func (p *Persister) listUserAuthenticatedClients(ctx context.Context, subject, sid, channel string) ([]client.Client, error) { +func (p *Persister) listUserSessionAuthenticatedClients(ctx context.Context, subject, sid, channel string) ([]client.LoginSessionClient, error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.listUserAuthenticatedClients") defer span.End() - var cs []client.Client + var cs []client.LoginSessionClient return cs, p.transaction(ctx, func(ctx context.Context, c *pop.Connection) error { if err := c.RawQuery( /* #nosec G201 - channel can either be "front" or "back" */ fmt.Sprintf(` -SELECT DISTINCT c.* FROM hydra_client as c +SELECT DISTINCT c.*, f.login_session_id FROM hydra_client as c JOIN hydra_oauth2_flow as f ON (c.id = f.client_id) WHERE f.subject=? AND @@ -581,6 +601,37 @@ WHERE }) } +func (p *Persister) listUserAuthenticatedClients(ctx context.Context, subject, channel string) ([]client.LoginSessionClient, error) { + ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.listUserAuthenticatedClients") + defer span.End() + + var cs []client.LoginSessionClient + return cs, p.transaction(ctx, func(ctx context.Context, c *pop.Connection) error { + if err := c.RawQuery( + /* #nosec G201 - channel can either be "front" or "back" */ + fmt.Sprintf(` +SELECT DISTINCT c.*, f.login_session_id FROM hydra_client as c +JOIN hydra_oauth2_flow as f ON (c.id = f.client_id) +WHERE + f.subject=? AND + c.%schannel_logout_uri!='' AND + c.%schannel_logout_uri IS NOT NULL AND + f.nid = ? AND + c.nid = ?`, + channel, + channel, + ), + subject, + p.NetworkID(ctx), + p.NetworkID(ctx), + ).All(&cs); err != nil { + return sqlcon.HandleError(err) + } + + return nil + }) +} + func (p *Persister) CreateLogoutRequest(ctx context.Context, request *consent.LogoutRequest) error { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.CreateLogoutRequest") defer span.End() diff --git a/persistence/sql/persister_nid_test.go b/persistence/sql/persister_nid_test.go index 29f4e613f72..b39a9125cfc 100644 --- a/persistence/sql/persister_nid_test.go +++ b/persistence/sql/persister_nid_test.go @@ -1514,11 +1514,11 @@ func (s *PersisterTestSuite) TestListUserAuthenticatedClientsWithBackChannelLogo _, err = r.Persister().HandleConsentRequest(s.t2, &consent.AcceptOAuth2ConsentRequest{ID: t2f2.ID, HandledAt: sqlxx.NullTime(time.Now()), Remember: true}) require.NoError(t, err) - cs, err := r.Persister().ListUserAuthenticatedClientsWithBackChannelLogout(s.t1, "sub", t1f1.SessionID.String()) + cs, err := r.Persister().ListUserSessionAuthenticatedClientsWithBackChannelLogout(s.t1, "sub", t1f1.SessionID.String()) require.NoError(t, err) require.Equal(t, 1, len(cs)) - cs, err = r.Persister().ListUserAuthenticatedClientsWithBackChannelLogout(s.t2, "sub", t1f1.SessionID.String()) + cs, err = r.Persister().ListUserSessionAuthenticatedClientsWithBackChannelLogout(s.t2, "sub", t1f1.SessionID.String()) require.NoError(t, err) require.Equal(t, 2, len(cs)) }) @@ -1568,11 +1568,11 @@ func (s *PersisterTestSuite) TestListUserAuthenticatedClientsWithFrontChannelLog _, err = r.Persister().HandleConsentRequest(s.t2, &consent.AcceptOAuth2ConsentRequest{ID: t2f2.ID, HandledAt: sqlxx.NullTime(time.Now()), Remember: true}) require.NoError(t, err) - cs, err := r.Persister().ListUserAuthenticatedClientsWithFrontChannelLogout(s.t1, "sub", t1f1.SessionID.String()) + cs, err := r.Persister().ListUserSessionAuthenticatedClientsWithFrontChannelLogout(s.t1, "sub", t1f1.SessionID.String()) require.NoError(t, err) require.Equal(t, 1, len(cs)) - cs, err = r.Persister().ListUserAuthenticatedClientsWithFrontChannelLogout(s.t2, "sub", t1f1.SessionID.String()) + cs, err = r.Persister().ListUserSessionAuthenticatedClientsWithFrontChannelLogout(s.t2, "sub", t1f1.SessionID.String()) require.NoError(t, err) require.Equal(t, 2, len(cs)) }) diff --git a/spec/api.json b/spec/api.json index ca7a9fd2ead..9f2e46a158d 100644 --- a/spec/api.json +++ b/spec/api.json @@ -2746,6 +2746,14 @@ "type": "string" } }, + { + "description": "If set, deletes only those consent sessions by the Subject that have been granted to the specified session id. Can be combined with client or all parameter.", + "in": "query", + "name": "login_session_id", + "schema": { + "type": "string" + } + }, { "description": "Revoke All Consent Sessions\n\nIf set to `true` deletes all consent sessions by the Subject that have been granted.", "in": "query", @@ -2753,6 +2761,14 @@ "schema": { "type": "boolean" } + }, + { + "description": "If set to `?trigger_back_channel_logout=true`, performs back channel logout for matching clients", + "in": "query", + "name": "trigger_back_channel_logout", + "schema": { + "type": "boolean" + } } ], "responses": { diff --git a/spec/swagger.json b/spec/swagger.json index 4c7fa469a32..cefe234aa2a 100755 --- a/spec/swagger.json +++ b/spec/swagger.json @@ -1258,11 +1258,23 @@ "name": "client", "in": "query" }, + { + "type": "string", + "description": "If set, deletes only those consent sessions by the Subject that have been granted to the specified session id. Can be combined with client or all parameter.", + "name": "login_session_id", + "in": "query" + }, { "type": "boolean", "description": "Revoke All Consent Sessions\n\nIf set to `true` deletes all consent sessions by the Subject that have been granted.", "name": "all", "in": "query" + }, + { + "type": "boolean", + "description": "If set to `?trigger_back_channel_logout=true`, performs back channel logout for matching clients", + "name": "trigger_back_channel_logout", + "in": "query" } ], "responses": {