2018-05-14 11:27:30 -04:00
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2019-11-29 12:59:40 +01:00
// See LICENSE.txt for license information.
2018-05-14 11:27:30 -04:00
package web
import (
b64 "encoding/base64"
2021-06-10 17:40:22 +05:30
"html"
2018-05-14 11:27:30 -04:00
"net/http"
2020-06-30 10:34:05 -04:00
"strconv"
2018-05-14 11:27:30 -04:00
"strings"
2021-07-22 12:21:47 +05:30
"github.com/mattermost/mattermost-server/v6/audit"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
"github.com/mattermost/mattermost-server/v6/utils"
2018-05-14 11:27:30 -04:00
)
2022-01-31 17:34:18 +04:00
const maxSAMLResponseSize = 2 * 1024 * 1024 // 2MB
2018-05-14 11:27:30 -04:00
func ( w * Web ) InitSaml ( ) {
2021-08-16 19:46:44 +02:00
w . MainRouter . Handle ( "/login/sso/saml" , w . APIHandler ( loginWithSaml ) ) . Methods ( "GET" )
w . MainRouter . Handle ( "/login/sso/saml" , w . APIHandlerTrustRequester ( completeSaml ) ) . Methods ( "POST" )
2018-05-14 11:27:30 -04:00
}
func loginWithSaml ( c * Context , w http . ResponseWriter , r * http . Request ) {
2020-02-13 13:26:58 +01:00
samlInterface := c . App . Saml ( )
2018-05-14 11:27:30 -04:00
if samlInterface == nil {
c . Err = model . NewAppError ( "loginWithSaml" , "api.user.saml.not_available.app_error" , nil , "" , http . StatusFound )
return
}
teamId , err := c . App . GetTeamIdFromQuery ( r . URL . Query ( ) )
if err != nil {
c . Err = err
return
}
action := r . URL . Query ( ) . Get ( "action" )
2021-07-12 20:05:36 +02:00
isMobile := action == model . OAuthActionMobile
2021-06-10 17:40:22 +05:30
redirectURL := html . EscapeString ( r . URL . Query ( ) . Get ( "redirect_to" ) )
2018-05-14 11:27:30 -04:00
relayProps := map [ string ] string { }
relayState := ""
2020-12-22 19:20:59 +05:30
if action != "" {
2018-05-14 11:27:30 -04:00
relayProps [ "team_id" ] = teamId
relayProps [ "action" ] = action
2021-07-12 20:05:36 +02:00
if action == model . OAuthActionEmailToSSO {
2018-05-14 11:27:30 -04:00
relayProps [ "email" ] = r . URL . Query ( ) . Get ( "email" )
}
}
2021-01-19 21:16:22 +05:30
if redirectURL != "" {
if isMobile && ! utils . IsValidMobileAuthRedirectURL ( c . App . Config ( ) , redirectURL ) {
invalidSchemeErr := model . NewAppError ( "loginWithOAuth" , "api.invalid_custom_url_scheme" , nil , "" , http . StatusBadRequest )
utils . RenderMobileError ( c . App . Config ( ) , w , invalidSchemeErr , redirectURL )
return
}
relayProps [ "redirect_to" ] = redirectURL
2018-05-14 11:27:30 -04:00
}
2021-07-12 20:05:36 +02:00
relayProps [ model . UserAuthServiceIsMobile ] = strconv . FormatBool ( isMobile )
2020-06-30 10:34:05 -04:00
2018-05-14 11:27:30 -04:00
if len ( relayProps ) > 0 {
2021-09-01 14:43:12 +02:00
relayState = b64 . StdEncoding . EncodeToString ( [ ] byte ( model . MapToJSON ( relayProps ) ) )
2018-05-14 11:27:30 -04:00
}
2020-12-21 21:20:47 +05:30
data , err := samlInterface . BuildRequest ( relayState )
if err != nil {
2018-05-14 11:27:30 -04:00
c . Err = err
return
}
2020-12-21 21:20:47 +05:30
w . Header ( ) . Set ( "Content-Type" , "application/x-www-form-urlencoded" )
http . Redirect ( w , r , data . URL , http . StatusFound )
2018-05-14 11:27:30 -04:00
}
func completeSaml ( c * Context , w http . ResponseWriter , r * http . Request ) {
2020-02-13 13:26:58 +01:00
samlInterface := c . App . Saml ( )
2018-05-14 11:27:30 -04:00
if samlInterface == nil {
c . Err = model . NewAppError ( "completeSaml" , "api.user.saml.not_available.app_error" , nil , "" , http . StatusFound )
return
}
//Validate that the user is with SAML and all that
encodedXML := r . FormValue ( "SAMLResponse" )
relayState := r . FormValue ( "RelayState" )
relayProps := make ( map [ string ] string )
2021-01-25 11:15:17 +01:00
if relayState != "" {
2018-05-14 11:27:30 -04:00
stateStr := ""
2020-12-21 21:20:47 +05:30
b , err := b64 . StdEncoding . DecodeString ( relayState )
if err != nil {
2018-05-14 11:27:30 -04:00
c . Err = model . NewAppError ( "completeSaml" , "api.user.authorize_oauth_user.invalid_state.app_error" , nil , err . Error ( ) , http . StatusFound )
return
}
2020-12-21 21:20:47 +05:30
stateStr = string ( b )
2021-09-01 14:43:12 +02:00
relayProps = model . MapFromJSON ( strings . NewReader ( stateStr ) )
2018-05-14 11:27:30 -04:00
}
2020-03-12 15:50:21 -04:00
auditRec := c . MakeAuditRecord ( "completeSaml" , audit . Fail )
defer c . LogAuditRec ( auditRec )
2019-10-29 10:54:43 -07:00
c . LogAudit ( "attempt" )
2018-05-14 11:27:30 -04:00
action := relayProps [ "action" ]
2020-03-12 15:50:21 -04:00
auditRec . AddMeta ( "action" , action )
2021-07-12 20:05:36 +02:00
isMobile := action == model . OAuthActionMobile
2021-01-19 21:16:22 +05:30
redirectURL := ""
hasRedirectURL := false
if val , ok := relayProps [ "redirect_to" ] ; ok {
redirectURL = val
2021-01-25 21:45:57 +05:30
hasRedirectURL = val != ""
2021-01-19 21:16:22 +05:30
}
2021-11-23 08:51:25 -08:00
redirectURL = fullyQualifiedRedirectURL ( c . GetSiteURLHeader ( ) , redirectURL )
2021-01-19 21:16:22 +05:30
handleError := func ( err * model . AppError ) {
2021-02-21 12:28:04 +05:30
if isMobile && hasRedirectURL {
2021-05-11 13:00:44 +03:00
err . Translate ( c . AppContext . T )
2021-02-21 12:28:04 +05:30
utils . RenderMobileError ( c . App . Config ( ) , w , err , redirectURL )
2018-05-14 11:27:30 -04:00
} else {
c . Err = err
c . Err . StatusCode = http . StatusFound
}
2021-01-19 21:16:22 +05:30
}
2022-01-31 17:34:18 +04:00
if len ( encodedXML ) > maxSAMLResponseSize {
err := model . NewAppError ( "completeSaml" , "api.user.authorize_oauth_user.saml_response_too_long.app_error" , nil , "SAML response is too long" , http . StatusBadRequest )
mlog . Error ( err . Error ( ) )
handleError ( err )
return
}
2021-05-11 13:00:44 +03:00
user , err := samlInterface . DoLogin ( c . AppContext , encodedXML , relayProps )
2021-01-19 21:16:22 +05:30
if err != nil {
c . LogAudit ( "fail" )
mlog . Error ( err . Error ( ) )
handleError ( err )
2018-05-14 11:27:30 -04:00
return
2019-10-31 12:50:43 +01:00
}
2018-05-14 11:27:30 -04:00
2019-10-31 12:50:43 +01:00
if err = c . App . CheckUserAllAuthenticationCriteria ( user , "" ) ; err != nil {
2021-01-19 21:16:22 +05:30
mlog . Error ( err . Error ( ) )
handleError ( err )
2019-10-31 12:50:43 +01:00
return
}
switch action {
2021-07-12 20:05:36 +02:00
case model . OAuthActionSignup :
2020-11-25 19:01:32 +05:30
if teamId := relayProps [ "team_id" ] ; teamId != "" {
2021-05-11 13:00:44 +03:00
if err = c . App . AddUserToTeamByTeamId ( c . AppContext , teamId , user ) ; err != nil {
2021-01-04 17:02:34 +03:00
c . LogErrorByCode ( err )
2020-11-25 19:01:32 +05:30
break
}
c . App . AddDirectChannels ( teamId , user )
2018-05-14 11:27:30 -04:00
}
2021-07-12 20:05:36 +02:00
case model . OAuthActionEmailToSSO :
2019-10-31 12:50:43 +01:00
if err = c . App . RevokeAllSessions ( user . Id ) ; err != nil {
2018-05-14 11:27:30 -04:00
c . Err = err
return
}
2020-03-12 15:50:21 -04:00
auditRec . AddMeta ( "revoked_user_id" , user . Id )
auditRec . AddMeta ( "revoked" , "Revoked all sessions for user" )
2019-10-31 12:50:43 +01:00
c . LogAuditWithUserId ( user . Id , "Revoked all sessions for user" )
2020-02-13 13:26:58 +01:00
c . App . Srv ( ) . Go ( func ( ) {
2021-07-12 20:05:36 +02:00
if err := c . App . Srv ( ) . EmailService . SendSignInChangeEmail ( user . Email , strings . Title ( model . UserAuthServiceSaml ) + " SSO" , user . Locale , c . App . GetSiteURL ( ) ) ; err != nil {
2021-07-19 18:26:06 +03:00
c . LogErrorByCode ( model . NewAppError ( "SendSignInChangeEmail" , "api.user.send_sign_in_change_email_and_forget.error" , nil , err . Error ( ) , http . StatusInternalServerError ) )
2019-10-31 12:50:43 +01:00
}
} )
}
2018-05-14 11:27:30 -04:00
2020-03-12 15:50:21 -04:00
auditRec . AddMeta ( "obtained_user_id" , user . Id )
2019-10-31 12:50:43 +01:00
c . LogAuditWithUserId ( user . Id , "obtained user" )
2019-10-29 10:54:43 -07:00
2021-05-11 13:00:44 +03:00
err = c . App . DoLogin ( c . AppContext , w , r , user , "" , isMobile , false , true )
2019-10-31 12:50:43 +01:00
if err != nil {
2021-01-19 21:16:22 +05:30
mlog . Error ( err . Error ( ) )
handleError ( err )
2019-10-31 12:50:43 +01:00
return
}
2019-06-11 15:09:00 -04:00
2020-03-12 15:50:21 -04:00
auditRec . Success ( )
2019-10-31 12:50:43 +01:00
c . LogAuditWithUserId ( user . Id , "success" )
2018-05-14 11:27:30 -04:00
2021-05-11 13:00:44 +03:00
c . App . AttachSessionCookies ( c . AppContext , w , r )
2018-05-14 11:27:30 -04:00
2021-01-19 21:16:22 +05:30
if hasRedirectURL {
if isMobile {
// Mobile clients with redirect url support
redirectURL = utils . AppendQueryParamsToURL ( redirectURL , map [ string ] string {
2021-07-12 20:05:36 +02:00
model . SessionCookieToken : c . AppContext . Session ( ) . Token ,
model . SessionCookieCsrf : c . AppContext . Session ( ) . GetCSRF ( ) ,
2021-01-19 21:16:22 +05:30
} )
utils . RenderMobileAuthComplete ( w , redirectURL )
} else {
http . Redirect ( w , r , redirectURL , http . StatusFound )
}
2019-10-31 12:50:43 +01:00
return
}
switch action {
2021-01-19 21:16:22 +05:30
// Mobile clients with web view implementation
2021-07-12 20:05:36 +02:00
case model . OAuthActionMobile :
2019-10-31 12:50:43 +01:00
ReturnStatusOK ( w )
2021-07-12 20:05:36 +02:00
case model . OAuthActionEmailToSSO :
2019-10-31 12:50:43 +01:00
http . Redirect ( w , r , c . GetSiteURLHeader ( ) + "/login?extra=signin_change" , http . StatusFound )
default :
http . Redirect ( w , r , c . GetSiteURLHeader ( ) , http . StatusFound )
2018-05-14 11:27:30 -04:00
}
}