2022-10-06 20:13:30 +05:30
package api
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
2023-10-19 14:16:20 +05:30
"io"
2022-10-06 20:13:30 +05:30
"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-03-20 21:01:41 +05:30
"github.com/SigNoz/signoz/ee/query-service/constants"
"github.com/SigNoz/signoz/ee/query-service/model"
2025-05-14 23:12:55 +05:30
"github.com/SigNoz/signoz/pkg/http/render"
2022-10-06 20:13:30 +05:30
)
func parseRequest ( r * http . Request , req interface { } ) error {
defer r . Body . Close ( )
2023-10-19 14:16:20 +05:30
requestBody , err := io . ReadAll ( r . Body )
2022-10-06 20:13:30 +05:30
if err != nil {
return err
}
err = json . Unmarshal ( requestBody , & req )
return err
}
// loginUser overrides base handler and considers SSO case.
func ( ah * APIHandler ) loginUser ( w http . ResponseWriter , r * http . Request ) {
2025-05-14 23:12:55 +05:30
r , err := ah . updateRequestContext ( w , r )
2023-02-24 14:57:07 +05:30
if err != nil {
2025-05-14 23:12:55 +05:30
render . Error ( w , err )
2023-02-24 14:57:07 +05:30
return
2022-10-06 20:13:30 +05:30
}
2025-05-14 23:12:55 +05:30
ah . Signoz . Handlers . User . Login ( w , r )
return
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 )
}
// receiveGoogleAuth completes google OAuth response and forwards a request
2023-02-24 14:57:07 +05:30
// to front-end to sign user in
2022-12-06 22:32:59 +05:30
func ( ah * APIHandler ) receiveGoogleAuth ( w http . ResponseWriter , r * http . Request ) {
2022-10-06 20:13:30 +05:30
redirectUri := constants . GetDefaultSiteURL ( )
ctx := context . Background ( )
2022-12-06 22:32:59 +05:30
if ! ah . CheckFeature ( model . SSO ) {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "[receiveGoogleAuth] sso requested but feature unavailable in org domain" )
2022-12-06 22:32:59 +05:30
http . Redirect ( w , r , fmt . Sprintf ( "%s?ssoerror=%s" , redirectUri , "feature unavailable, please upgrade your billing plan to access this feature" ) , http . StatusMovedPermanently )
return
}
q := r . URL . Query ( )
if errType := q . Get ( "error" ) ; errType != "" {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "[receiveGoogleAuth] failed to login with google auth" , zap . String ( "error" , errType ) , zap . String ( "error_description" , q . Get ( "error_description" ) ) )
2022-12-06 22:32:59 +05:30
http . Redirect ( w , r , fmt . Sprintf ( "%s?ssoerror=%s" , redirectUri , "failed to login through SSO " ) , http . StatusMovedPermanently )
return
}
relayState := q . Get ( "state" )
2024-03-27 00:07:29 +05:30
zap . L ( ) . Debug ( "[receiveGoogleAuth] relay state received" , zap . String ( "state" , relayState ) )
2022-10-06 20:13:30 +05:30
2022-12-06 22:32:59 +05:30
parsedState , err := url . Parse ( relayState )
if err != nil || relayState == "" {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "[receiveGoogleAuth] 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 )
return
}
2022-10-06 20:13:30 +05:30
2022-12-06 22:32:59 +05:30
// 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.
2022-12-06 22:32:59 +05:30
domain , err := ah . AppDao ( ) . GetDomainFromSsoResponse ( ctx , parsedState )
if err != nil {
handleSsoError ( w , r , redirectUri )
return
2022-10-06 20:13:30 +05:30
}
2023-02-24 14:57:07 +05:30
// now that we have domain, use domain to fetch sso settings.
// prepare google callback handler using parsedState -
2022-12-06 22:32:59 +05:30
// which contains redirect URL (front-end endpoint)
callbackHandler , err := domain . PrepareGoogleOAuthProvider ( parsedState )
2024-06-11 20:10:38 +05:30
if err != nil {
zap . L ( ) . Error ( "[receiveGoogleAuth] failed to prepare google oauth provider" , zap . String ( "domain" , domain . String ( ) ) , zap . Error ( err ) )
handleSsoError ( w , r , redirectUri )
return
}
2022-12-06 22:32:59 +05:30
identity , err := callbackHandler . HandleCallback ( r )
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "[receiveGoogleAuth] failed to process HandleCallback " , zap . String ( "domain" , domain . String ( ) ) , zap . Error ( err ) )
2022-12-06 22:32:59 +05:30
handleSsoError ( w , r , redirectUri )
return
}
2023-02-24 14:57:07 +05:30
2025-05-14 23:12:55 +05:30
nextPage , err := ah . Signoz . Modules . User . PrepareSsoRedirect ( ctx , redirectUri , identity . Email , ah . opts . JWT )
2022-12-06 22:32:59 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "[receiveGoogleAuth] 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 )
return
}
http . Redirect ( w , r , nextPage , http . StatusSeeOther )
}
// receiveSAML completes a SAML request and gets user logged in
func ( ah * APIHandler ) receiveSAML ( w http . ResponseWriter , r * http . Request ) {
// this is the source url that initiated the login request
redirectUri := constants . GetDefaultSiteURL ( )
ctx := context . Background ( )
2022-10-06 20:13:30 +05:30
if ! ah . CheckFeature ( model . SSO ) {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "[receiveSAML] sso requested but feature unavailable in org domain" )
2022-10-06 20:13:30 +05:30
http . Redirect ( w , r , fmt . Sprintf ( "%s?ssoerror=%s" , redirectUri , "feature unavailable, please upgrade your billing plan to access this feature" ) , http . StatusMovedPermanently )
return
}
err := r . ParseForm ( )
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.
2022-12-06 22:32:59 +05:30
domain , err := ah . AppDao ( ) . 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
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
}