2022-10-06 20:13:30 +05:30
package api
import (
"context"
"encoding/base64"
"fmt"
"net/http"
"net/url"
2023-02-24 14:57:07 +05:30
2023-10-19 14:16:20 +05:30
"go.uber.org/zap"
2025-05-25 14:16:42 +05:30
"github.com/SigNoz/signoz/pkg/query-service/constants"
"github.com/SigNoz/signoz/pkg/valuer"
2022-10-06 20:13:30 +05:30
)
2022-12-06 22:32:59 +05:30
func handleSsoError ( w http . ResponseWriter , r * http . Request , redirectURL string ) {
ssoError := [ ] byte ( "Login failed. Please contact your system administrator" )
dst := make ( [ ] byte , base64 . StdEncoding . EncodedLen ( len ( ssoError ) ) )
base64 . StdEncoding . Encode ( dst , ssoError )
http . Redirect ( w , r , fmt . Sprintf ( "%s?ssoerror=%s" , redirectURL , string ( dst ) ) , http . StatusSeeOther )
}
2025-05-25 14:16:42 +05:30
// receiveSAML completes a SAML request and gets user logged in
func ( ah * APIHandler ) receiveSAML ( w http . ResponseWriter , r * http . Request ) {
2022-12-06 22:32:59 +05:30
// this is the source url that initiated the login request
redirectUri := constants . GetDefaultSiteURL ( )
ctx := context . Background ( )
2025-05-28 18:21:35 +05:30
err := r . ParseForm ( )
2022-10-06 20:13:30 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "[receiveSAML] failed to process response - invalid response from IDP" , zap . Error ( err ) , zap . Any ( "request" , r ) )
2022-12-06 22:32:59 +05:30
handleSsoError ( w , r , redirectUri )
2022-10-06 20:13:30 +05:30
return
}
// the relay state is sent when a login request is submitted to
// Idp.
relayState := r . FormValue ( "RelayState" )
2024-03-27 00:07:29 +05:30
zap . L ( ) . Debug ( "[receiveML] relay state" , zap . String ( "relayState" , relayState ) )
2022-10-06 20:13:30 +05:30
parsedState , err := url . Parse ( relayState )
if err != nil || relayState == "" {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "[receiveSAML] failed to process response - invalid response from IDP" , zap . Error ( err ) , zap . Any ( "request" , r ) )
2022-12-06 22:32:59 +05:30
handleSsoError ( w , r , redirectUri )
2022-10-06 20:13:30 +05:30
return
}
// upgrade redirect url from the relay state for better accuracy
redirectUri = fmt . Sprintf ( "%s://%s%s" , parsedState . Scheme , parsedState . Host , "/login" )
2023-02-24 14:57:07 +05:30
// fetch domain by parsing relay state.
2025-05-25 14:16:42 +05:30
domain , err := ah . Signoz . Modules . User . GetDomainFromSsoResponse ( ctx , parsedState )
2022-10-06 20:13:30 +05:30
if err != nil {
2022-12-06 22:32:59 +05:30
handleSsoError ( w , r , redirectUri )
2022-10-06 20:13:30 +05:30
return
}
2023-02-24 14:57:07 +05:30
2025-05-28 18:21:35 +05:30
orgID , err := valuer . NewUUID ( domain . OrgID )
if err != nil {
handleSsoError ( w , r , redirectUri )
return
}
_ , err = ah . Signoz . Licensing . GetActive ( ctx , orgID )
if err != nil {
zap . L ( ) . Error ( "[receiveSAML] sso requested but feature unavailable in org domain" )
http . Redirect ( w , r , fmt . Sprintf ( "%s?ssoerror=%s" , redirectUri , "feature unavailable, please upgrade your billing plan to access this feature" ) , http . StatusMovedPermanently )
return
}
2022-10-06 20:13:30 +05:30
sp , err := domain . PrepareSamlRequest ( parsedState )
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "[receiveSAML] failed to prepare saml request for domain" , zap . String ( "domain" , domain . String ( ) ) , zap . Error ( err ) )
2022-12-06 22:32:59 +05:30
handleSsoError ( w , r , redirectUri )
2022-10-06 20:13:30 +05:30
return
}
assertionInfo , err := sp . RetrieveAssertionInfo ( r . FormValue ( "SAMLResponse" ) )
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "[receiveSAML] failed to retrieve assertion info from saml response" , zap . String ( "domain" , domain . String ( ) ) , zap . Error ( err ) )
2022-12-06 22:32:59 +05:30
handleSsoError ( w , r , redirectUri )
2022-10-06 20:13:30 +05:30
return
}
if assertionInfo . WarningInfo . InvalidTime {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "[receiveSAML] expired saml response" , zap . String ( "domain" , domain . String ( ) ) , zap . Error ( err ) )
2022-12-06 22:32:59 +05:30
handleSsoError ( w , r , redirectUri )
2022-10-06 20:13:30 +05:30
return
}
email := assertionInfo . NameID
2022-12-06 22:32:59 +05:30
if email == "" {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "[receiveSAML] invalid email in the SSO response" , zap . String ( "domain" , domain . String ( ) ) )
2022-12-06 22:32:59 +05:30
handleSsoError ( w , r , redirectUri )
2022-10-06 20:13:30 +05:30
return
}
2025-05-14 23:12:55 +05:30
nextPage , err := ah . Signoz . Modules . User . PrepareSsoRedirect ( ctx , redirectUri , email , ah . opts . JWT )
2022-10-06 20:13:30 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "[receiveSAML] failed to generate redirect URI after successful login " , zap . String ( "domain" , domain . String ( ) ) , zap . Error ( err ) )
2022-12-06 22:32:59 +05:30
handleSsoError ( w , r , redirectUri )
2022-10-06 20:13:30 +05:30
return
}
2023-02-24 14:57:07 +05:30
2022-12-06 22:32:59 +05:30
http . Redirect ( w , r , nextPage , http . StatusSeeOther )
2022-10-06 20:13:30 +05:30
}