mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-06-04 11:25:52 +08:00

* feat(license): base setup for license service * feat(license): delete old manager and import to new * feat(license): deal with features * feat(license): complete the license service in ee * feat(license): add sqlmigration for licenses * feat(license): remove feature flags * feat(license): refactor into provider pattern * feat(license): remove the ff lookup interface * feat(license): add logging to the validator functions * feat(license): implement features for OSS build * feat(license): fix the OSS build * feat(license): lets blast frontend * feat(license): fix the EE OSS build without license * feat(license): remove the hardcoded testing configs * feat(license): upgrade migration to 34 * feat(license): better naming and structure * feat(license): better naming and structure * feat(license): better naming and structure * feat(license): better naming and structure * feat(license): better naming and structure * feat(license): better naming and structure * feat(license): better naming and structure * feat(license): integration tests * feat(license): integration tests * feat(license): refactor frontend * feat(license): make frontend api structure changes * feat(license): fix integration tests * feat(license): revert hardcoded configs * feat(license): fix integration tests * feat(license): address review comments * feat(license): address review comments * feat(license): address review comments * feat(license): address review comments * feat(license): update migration * feat(license): update migration * feat(license): update migration * feat(license): fixed logging * feat(license): use the unmarshaller for postable subscription * feat(license): correct the error message * feat(license): fix license test * feat(license): fix lint issues * feat(user): do not kill the service if upstream is down
191 lines
6.6 KiB
Go
191 lines
6.6 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/SigNoz/signoz/ee/query-service/constants"
|
|
"github.com/SigNoz/signoz/pkg/http/render"
|
|
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
|
)
|
|
|
|
func parseRequest(r *http.Request, req interface{}) error {
|
|
defer r.Body.Close()
|
|
requestBody, err := io.ReadAll(r.Body)
|
|
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) {
|
|
r, err := ah.updateRequestContext(w, r)
|
|
if err != nil {
|
|
render.Error(w, err)
|
|
return
|
|
}
|
|
ah.Signoz.Handlers.User.Login(w, r)
|
|
}
|
|
|
|
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
|
|
// to front-end to sign user in
|
|
func (ah *APIHandler) receiveGoogleAuth(w http.ResponseWriter, r *http.Request) {
|
|
redirectUri := constants.GetDefaultSiteURL()
|
|
ctx := context.Background()
|
|
|
|
if !ah.CheckFeature(r.Context(), licensetypes.SSO) {
|
|
zap.L().Error("[receiveGoogleAuth] 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
|
|
}
|
|
|
|
q := r.URL.Query()
|
|
if errType := q.Get("error"); errType != "" {
|
|
zap.L().Error("[receiveGoogleAuth] failed to login with google auth", zap.String("error", errType), zap.String("error_description", q.Get("error_description")))
|
|
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "failed to login through SSO "), http.StatusMovedPermanently)
|
|
return
|
|
}
|
|
|
|
relayState := q.Get("state")
|
|
zap.L().Debug("[receiveGoogleAuth] relay state received", zap.String("state", relayState))
|
|
|
|
parsedState, err := url.Parse(relayState)
|
|
if err != nil || relayState == "" {
|
|
zap.L().Error("[receiveGoogleAuth] failed to process response - invalid response from IDP", zap.Error(err), zap.Any("request", r))
|
|
handleSsoError(w, r, redirectUri)
|
|
return
|
|
}
|
|
|
|
// upgrade redirect url from the relay state for better accuracy
|
|
redirectUri = fmt.Sprintf("%s://%s%s", parsedState.Scheme, parsedState.Host, "/login")
|
|
|
|
// fetch domain by parsing relay state.
|
|
domain, err := ah.AppDao().GetDomainFromSsoResponse(ctx, parsedState)
|
|
if err != nil {
|
|
handleSsoError(w, r, redirectUri)
|
|
return
|
|
}
|
|
|
|
// now that we have domain, use domain to fetch sso settings.
|
|
// prepare google callback handler using parsedState -
|
|
// which contains redirect URL (front-end endpoint)
|
|
callbackHandler, err := domain.PrepareGoogleOAuthProvider(parsedState)
|
|
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
|
|
}
|
|
|
|
identity, err := callbackHandler.HandleCallback(r)
|
|
if err != nil {
|
|
zap.L().Error("[receiveGoogleAuth] failed to process HandleCallback ", zap.String("domain", domain.String()), zap.Error(err))
|
|
handleSsoError(w, r, redirectUri)
|
|
return
|
|
}
|
|
|
|
nextPage, err := ah.Signoz.Modules.User.PrepareSsoRedirect(ctx, redirectUri, identity.Email, ah.opts.JWT)
|
|
if err != nil {
|
|
zap.L().Error("[receiveGoogleAuth] failed to generate redirect URI after successful login ", zap.String("domain", domain.String()), zap.Error(err))
|
|
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()
|
|
|
|
if !ah.CheckFeature(r.Context(), licensetypes.SSO) {
|
|
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
|
|
}
|
|
|
|
err := r.ParseForm()
|
|
if err != nil {
|
|
zap.L().Error("[receiveSAML] failed to process response - invalid response from IDP", zap.Error(err), zap.Any("request", r))
|
|
handleSsoError(w, r, redirectUri)
|
|
return
|
|
}
|
|
|
|
// the relay state is sent when a login request is submitted to
|
|
// Idp.
|
|
relayState := r.FormValue("RelayState")
|
|
zap.L().Debug("[receiveML] relay state", zap.String("relayState", relayState))
|
|
|
|
parsedState, err := url.Parse(relayState)
|
|
if err != nil || relayState == "" {
|
|
zap.L().Error("[receiveSAML] failed to process response - invalid response from IDP", zap.Error(err), zap.Any("request", r))
|
|
handleSsoError(w, r, redirectUri)
|
|
return
|
|
}
|
|
|
|
// upgrade redirect url from the relay state for better accuracy
|
|
redirectUri = fmt.Sprintf("%s://%s%s", parsedState.Scheme, parsedState.Host, "/login")
|
|
|
|
// fetch domain by parsing relay state.
|
|
domain, err := ah.AppDao().GetDomainFromSsoResponse(ctx, parsedState)
|
|
if err != nil {
|
|
handleSsoError(w, r, redirectUri)
|
|
return
|
|
}
|
|
|
|
sp, err := domain.PrepareSamlRequest(parsedState)
|
|
if err != nil {
|
|
zap.L().Error("[receiveSAML] failed to prepare saml request for domain", zap.String("domain", domain.String()), zap.Error(err))
|
|
handleSsoError(w, r, redirectUri)
|
|
return
|
|
}
|
|
|
|
assertionInfo, err := sp.RetrieveAssertionInfo(r.FormValue("SAMLResponse"))
|
|
if err != nil {
|
|
zap.L().Error("[receiveSAML] failed to retrieve assertion info from saml response", zap.String("domain", domain.String()), zap.Error(err))
|
|
handleSsoError(w, r, redirectUri)
|
|
return
|
|
}
|
|
|
|
if assertionInfo.WarningInfo.InvalidTime {
|
|
zap.L().Error("[receiveSAML] expired saml response", zap.String("domain", domain.String()), zap.Error(err))
|
|
handleSsoError(w, r, redirectUri)
|
|
return
|
|
}
|
|
|
|
email := assertionInfo.NameID
|
|
if email == "" {
|
|
zap.L().Error("[receiveSAML] invalid email in the SSO response", zap.String("domain", domain.String()))
|
|
handleSsoError(w, r, redirectUri)
|
|
return
|
|
}
|
|
|
|
nextPage, err := ah.Signoz.Modules.User.PrepareSsoRedirect(ctx, redirectUri, email, ah.opts.JWT)
|
|
if err != nil {
|
|
zap.L().Error("[receiveSAML] failed to generate redirect URI after successful login ", zap.String("domain", domain.String()), zap.Error(err))
|
|
handleSsoError(w, r, redirectUri)
|
|
return
|
|
}
|
|
|
|
http.Redirect(w, r, nextPage, http.StatusSeeOther)
|
|
}
|