github.com/minio/console@v1.4.1/api/user_logout.go (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2021 MinIO, Inc. 3 // 4 // This program is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Affero General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // This program is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <http://www.gnu.org/licenses/>. 16 17 package api 18 19 import ( 20 "context" 21 "encoding/base64" 22 "encoding/json" 23 "fmt" 24 "net/http" 25 "net/url" 26 "time" 27 28 "github.com/go-openapi/errors" 29 30 "github.com/go-openapi/runtime" 31 "github.com/go-openapi/runtime/middleware" 32 "github.com/minio/console/api/operations" 33 authApi "github.com/minio/console/api/operations/auth" 34 "github.com/minio/console/models" 35 "github.com/minio/console/pkg/auth/idp/oauth2" 36 ) 37 38 func registerLogoutHandlers(api *operations.ConsoleAPI) { 39 // logout from console 40 api.AuthLogoutHandler = authApi.LogoutHandlerFunc(func(params authApi.LogoutParams, session *models.Principal) middleware.Responder { 41 err := getLogoutResponse(session, params) 42 if err != nil { 43 api.Logger("IDP logout failed: %v", err.APIError.DetailedMessage) 44 } 45 // Custom response writer to expire the session cookies 46 return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) { 47 if err != nil { 48 w.Header().Set("IDP-Logout", fmt.Sprintf("%v", err.APIError.DetailedMessage)) 49 } 50 expiredCookie := ExpireSessionCookie() 51 // this will tell the browser to clear the cookie and invalidate user session 52 // additionally we are deleting the cookie from the client side 53 http.SetCookie(w, &expiredCookie) 54 http.SetCookie(w, &http.Cookie{ 55 Path: "/", 56 Name: "idp-refresh-token", 57 Value: "", 58 MaxAge: -1, 59 Expires: time.Now().Add(-100 * time.Hour), 60 HttpOnly: true, 61 Secure: len(GlobalPublicCerts) > 0, 62 SameSite: http.SameSiteLaxMode, 63 }) 64 authApi.NewLogoutOK().WriteResponse(w, p) 65 }) 66 }) 67 } 68 69 // logout() call Expire() on the provided ConsoleCredentials 70 func logout(credentials ConsoleCredentialsI) { 71 credentials.Expire() 72 } 73 74 // getLogoutResponse performs logout() and returns nil or errors 75 func getLogoutResponse(session *models.Principal, params authApi.LogoutParams) *CodedAPIError { 76 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 77 defer cancel() 78 state := params.Body.State 79 if state != "" { 80 if err := logoutFromIDPProvider(params.HTTPRequest, state); err != nil { 81 return ErrorWithContext(ctx, err) 82 } 83 } 84 creds := getConsoleCredentialsFromSession(session) 85 credentials := ConsoleCredentials{ConsoleCredentials: creds} 86 logout(credentials) 87 return nil 88 } 89 90 func logoutFromIDPProvider(r *http.Request, state string) error { 91 decodedRState, err := base64.StdEncoding.DecodeString(state) 92 if err != nil { 93 return err 94 } 95 var requestItems oauth2.LoginURLParams 96 err = json.Unmarshal(decodedRState, &requestItems) 97 if err != nil { 98 return err 99 } 100 providerCfg := GlobalMinIOConfig.OpenIDProviders[requestItems.IDPName] 101 refreshToken, err := r.Cookie("idp-refresh-token") 102 if err != nil { 103 return err 104 } 105 if providerCfg.EndSessionEndpoint != "" { 106 params := url.Values{} 107 params.Add("client_id", providerCfg.ClientID) 108 params.Add("client_secret", providerCfg.ClientSecret) 109 params.Add("refresh_token", refreshToken.Value) 110 client := &http.Client{ 111 Transport: GlobalTransport, 112 } 113 result, err := client.PostForm(providerCfg.EndSessionEndpoint, params) 114 if err != nil { 115 return errors.New(500, "failed to logout: %v", err.Error()) 116 } 117 if result.StatusCode != 204 { 118 return errors.New(int32(result.StatusCode), "failed to logout") 119 } 120 } 121 122 return nil 123 }