mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-11 17:19:06 +08:00
feat: preference framework qs changes (#5527)
* feat: query service changes base setup for preferences * feat: added handlers for user and org preferences * chore: added base for all user and all org preferences * feat: added handlers for all user and all org preferences * feat: register the preference routes and initDB in pkg/query-service * feat: code refactor * chore: too much fun code refactor * chore: little little missing attributes * fix: handle range queries better * fix: handle range queries better * chore: address review comments * chore: use struct inheritance for the all preferences struct * chore: address review comments * chore: address review comments * chore: correct preference routes * chore: low hanging optimisations * chore: address review comments * chore: address review comments * chore: added extra validations for the check in allowed values * fix: better handling for the jwt claims * fix: better handling for the jwt claims * chore: move the error to preference apis * chore: move the error to preference apis * fix: move the 401 logic to the auth middleware
This commit is contained in:
parent
4360cd0397
commit
d00024b64a
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@ -27,6 +28,7 @@ import (
|
||||
"go.signoz.io/signoz/ee/query-service/integrations/gateway"
|
||||
"go.signoz.io/signoz/ee/query-service/interfaces"
|
||||
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||
|
||||
licensepkg "go.signoz.io/signoz/ee/query-service/license"
|
||||
@ -40,6 +42,7 @@ import (
|
||||
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
|
||||
"go.signoz.io/signoz/pkg/query-service/app/opamp"
|
||||
opAmpModel "go.signoz.io/signoz/pkg/query-service/app/opamp/model"
|
||||
"go.signoz.io/signoz/pkg/query-service/app/preferences"
|
||||
"go.signoz.io/signoz/pkg/query-service/cache"
|
||||
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
|
||||
"go.signoz.io/signoz/pkg/query-service/healthcheck"
|
||||
@ -109,6 +112,10 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
|
||||
baseexplorer.InitWithDSN(baseconst.RELATIONAL_DATASOURCE_PATH)
|
||||
|
||||
if err := preferences.InitDB(baseconst.RELATIONAL_DATASOURCE_PATH); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
localDB, err := dashboards.InitDB(baseconst.RELATIONAL_DATASOURCE_PATH)
|
||||
|
||||
if err != nil {
|
||||
@ -319,7 +326,17 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler) (*http.Server, e
|
||||
|
||||
// add auth middleware
|
||||
getUserFromRequest := func(r *http.Request) (*basemodel.UserPayload, error) {
|
||||
return auth.GetUserFromRequest(r, apiHandler)
|
||||
user, err := auth.GetUserFromRequest(r, apiHandler)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if user.User.OrgId == "" {
|
||||
return nil, model.UnauthorizedError(errors.New("orgId is missing in the claims"))
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
am := baseapp.NewAuthMiddleware(getUserFromRequest)
|
||||
|
||||
|
@ -29,12 +29,14 @@ import (
|
||||
logsv3 "go.signoz.io/signoz/pkg/query-service/app/logs/v3"
|
||||
"go.signoz.io/signoz/pkg/query-service/app/metrics"
|
||||
metricsv3 "go.signoz.io/signoz/pkg/query-service/app/metrics/v3"
|
||||
"go.signoz.io/signoz/pkg/query-service/app/preferences"
|
||||
"go.signoz.io/signoz/pkg/query-service/app/querier"
|
||||
querierV2 "go.signoz.io/signoz/pkg/query-service/app/querier/v2"
|
||||
"go.signoz.io/signoz/pkg/query-service/app/queryBuilder"
|
||||
tracesV3 "go.signoz.io/signoz/pkg/query-service/app/traces/v3"
|
||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||
"go.signoz.io/signoz/pkg/query-service/cache"
|
||||
"go.signoz.io/signoz/pkg/query-service/common"
|
||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||
"go.signoz.io/signoz/pkg/query-service/postprocess"
|
||||
@ -398,6 +400,22 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *AuthMiddleware) {
|
||||
|
||||
router.HandleFunc("/api/v1/disks", am.ViewAccess(aH.getDisks)).Methods(http.MethodGet)
|
||||
|
||||
// === Preference APIs ===
|
||||
|
||||
// user actions
|
||||
router.HandleFunc("/api/v1/user/preferences", am.ViewAccess(aH.getAllUserPreferences)).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/api/v1/user/preferences/{preferenceId}", am.ViewAccess(aH.getUserPreference)).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/api/v1/user/preferences/{preferenceId}", am.ViewAccess(aH.updateUserPreference)).Methods(http.MethodPut)
|
||||
|
||||
// org actions
|
||||
router.HandleFunc("/api/v1/org/preferences", am.AdminAccess(aH.getAllOrgPreferences)).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/api/v1/org/preferences/{preferenceId}", am.AdminAccess(aH.getOrgPreference)).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/api/v1/org/preferences/{preferenceId}", am.AdminAccess(aH.updateOrgPreference)).Methods(http.MethodPut)
|
||||
|
||||
// === Authentication APIs ===
|
||||
router.HandleFunc("/api/v1/invite", am.AdminAccess(aH.inviteUser)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/invite/{token}", am.OpenAccess(aH.getInvite)).Methods(http.MethodGet)
|
||||
@ -2192,6 +2210,115 @@ func (aH *APIHandler) WriteJSON(w http.ResponseWriter, r *http.Request, response
|
||||
w.Write(resp)
|
||||
}
|
||||
|
||||
// Preferences
|
||||
|
||||
func (ah *APIHandler) getUserPreference(
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
) {
|
||||
preferenceId := mux.Vars(r)["preferenceId"]
|
||||
user := common.GetUserFromContext(r.Context())
|
||||
|
||||
preference, apiErr := preferences.GetUserPreference(
|
||||
r.Context(), preferenceId, user.User.OrgId, user.User.Id,
|
||||
)
|
||||
if apiErr != nil {
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
ah.Respond(w, preference)
|
||||
}
|
||||
|
||||
func (ah *APIHandler) updateUserPreference(
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
) {
|
||||
preferenceId := mux.Vars(r)["preferenceId"]
|
||||
user := common.GetUserFromContext(r.Context())
|
||||
req := preferences.UpdatePreference{}
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil {
|
||||
RespondError(w, model.BadRequest(err), nil)
|
||||
return
|
||||
}
|
||||
preference, apiErr := preferences.UpdateUserPreference(r.Context(), preferenceId, req.PreferenceValue, user.User.Id)
|
||||
if apiErr != nil {
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
ah.Respond(w, preference)
|
||||
}
|
||||
|
||||
func (ah *APIHandler) getAllUserPreferences(
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
) {
|
||||
user := common.GetUserFromContext(r.Context())
|
||||
preference, apiErr := preferences.GetAllUserPreferences(
|
||||
r.Context(), user.User.OrgId, user.User.Id,
|
||||
)
|
||||
if apiErr != nil {
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
ah.Respond(w, preference)
|
||||
}
|
||||
|
||||
func (ah *APIHandler) getOrgPreference(
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
) {
|
||||
preferenceId := mux.Vars(r)["preferenceId"]
|
||||
user := common.GetUserFromContext(r.Context())
|
||||
preference, apiErr := preferences.GetOrgPreference(
|
||||
r.Context(), preferenceId, user.User.OrgId,
|
||||
)
|
||||
if apiErr != nil {
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
ah.Respond(w, preference)
|
||||
}
|
||||
|
||||
func (ah *APIHandler) updateOrgPreference(
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
) {
|
||||
preferenceId := mux.Vars(r)["preferenceId"]
|
||||
req := preferences.UpdatePreference{}
|
||||
user := common.GetUserFromContext(r.Context())
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil {
|
||||
RespondError(w, model.BadRequest(err), nil)
|
||||
return
|
||||
}
|
||||
preference, apiErr := preferences.UpdateOrgPreference(r.Context(), preferenceId, req.PreferenceValue, user.User.OrgId)
|
||||
if apiErr != nil {
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
ah.Respond(w, preference)
|
||||
}
|
||||
|
||||
func (ah *APIHandler) getAllOrgPreferences(
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
) {
|
||||
user := common.GetUserFromContext(r.Context())
|
||||
preference, apiErr := preferences.GetAllOrgPreferences(
|
||||
r.Context(), user.User.OrgId,
|
||||
)
|
||||
if apiErr != nil {
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
ah.Respond(w, preference)
|
||||
}
|
||||
|
||||
// Integrations
|
||||
func (ah *APIHandler) RegisterIntegrationRoutes(router *mux.Router, am *AuthMiddleware) {
|
||||
subRouter := router.PathPrefix("/api/v1/integrations").Subrouter()
|
||||
|
37
pkg/query-service/app/preferences/map.go
Normal file
37
pkg/query-service/app/preferences/map.go
Normal file
@ -0,0 +1,37 @@
|
||||
package preferences
|
||||
|
||||
var preferenceMap = map[string]Preference{
|
||||
"DASHBOARDS_LIST_VIEW": {
|
||||
Key: "DASHBOARDS_LIST_VIEW",
|
||||
Name: "Dashboards List View",
|
||||
Description: "",
|
||||
ValueType: "string",
|
||||
DefaultValue: "grid",
|
||||
AllowedValues: []interface{}{"grid", "list"},
|
||||
IsDiscreteValues: true,
|
||||
AllowedScopes: []string{"user", "org"},
|
||||
},
|
||||
"LOGS_TOOLBAR_COLLAPSED": {
|
||||
Key: "LOGS_TOOLBAR_COLLAPSED",
|
||||
Name: "Logs toolbar",
|
||||
Description: "",
|
||||
ValueType: "boolean",
|
||||
DefaultValue: false,
|
||||
AllowedValues: []interface{}{true, false},
|
||||
IsDiscreteValues: true,
|
||||
AllowedScopes: []string{"user", "org"},
|
||||
},
|
||||
"MAX_DEPTH_ALLOWED": {
|
||||
Key: "MAX_DEPTH_ALLOWED",
|
||||
Name: "Max Depth Allowed",
|
||||
Description: "",
|
||||
ValueType: "integer",
|
||||
DefaultValue: 10,
|
||||
IsDiscreteValues: false,
|
||||
Range: Range{
|
||||
Min: 0,
|
||||
Max: 100,
|
||||
},
|
||||
AllowedScopes: []string{"user", "org"},
|
||||
},
|
||||
}
|
544
pkg/query-service/app/preferences/model.go
Normal file
544
pkg/query-service/app/preferences/model.go
Normal file
@ -0,0 +1,544 @@
|
||||
package preferences
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.signoz.io/signoz/ee/query-service/model"
|
||||
)
|
||||
|
||||
type Range struct {
|
||||
Min int64 `json:"min"`
|
||||
Max int64 `json:"max"`
|
||||
}
|
||||
|
||||
type Preference struct {
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
ValueType string `json:"valueType"`
|
||||
DefaultValue interface{} `json:"defaultValue"`
|
||||
AllowedValues []interface{} `json:"allowedValues"`
|
||||
IsDiscreteValues bool `json:"isDiscreteValues"`
|
||||
Range Range `json:"range"`
|
||||
AllowedScopes []string `json:"allowedScopes"`
|
||||
}
|
||||
|
||||
func (p *Preference) ErrorValueTypeMismatch() *model.ApiError {
|
||||
return &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("the preference value is not of expected type: %s", p.ValueType)}
|
||||
}
|
||||
|
||||
const (
|
||||
PreferenceValueTypeInteger string = "integer"
|
||||
PreferenceValueTypeFloat string = "float"
|
||||
PreferenceValueTypeString string = "string"
|
||||
PreferenceValueTypeBoolean string = "boolean"
|
||||
)
|
||||
|
||||
const (
|
||||
OrgAllowedScope string = "org"
|
||||
UserAllowedScope string = "user"
|
||||
)
|
||||
|
||||
func (p *Preference) checkIfInAllowedValues(preferenceValue interface{}) (bool, *model.ApiError) {
|
||||
|
||||
switch p.ValueType {
|
||||
case PreferenceValueTypeInteger:
|
||||
_, ok := preferenceValue.(int64)
|
||||
if !ok {
|
||||
return false, p.ErrorValueTypeMismatch()
|
||||
}
|
||||
case PreferenceValueTypeFloat:
|
||||
_, ok := preferenceValue.(float64)
|
||||
if !ok {
|
||||
return false, p.ErrorValueTypeMismatch()
|
||||
}
|
||||
case PreferenceValueTypeString:
|
||||
_, ok := preferenceValue.(string)
|
||||
if !ok {
|
||||
return false, p.ErrorValueTypeMismatch()
|
||||
}
|
||||
case PreferenceValueTypeBoolean:
|
||||
_, ok := preferenceValue.(bool)
|
||||
if !ok {
|
||||
return false, p.ErrorValueTypeMismatch()
|
||||
}
|
||||
}
|
||||
isInAllowedValues := false
|
||||
for _, value := range p.AllowedValues {
|
||||
switch p.ValueType {
|
||||
case PreferenceValueTypeInteger:
|
||||
allowedValue, ok := value.(int64)
|
||||
if !ok {
|
||||
return false, p.ErrorValueTypeMismatch()
|
||||
}
|
||||
|
||||
if allowedValue == preferenceValue {
|
||||
isInAllowedValues = true
|
||||
}
|
||||
case PreferenceValueTypeFloat:
|
||||
allowedValue, ok := value.(float64)
|
||||
if !ok {
|
||||
return false, p.ErrorValueTypeMismatch()
|
||||
}
|
||||
|
||||
if allowedValue == preferenceValue {
|
||||
isInAllowedValues = true
|
||||
}
|
||||
case PreferenceValueTypeString:
|
||||
allowedValue, ok := value.(string)
|
||||
if !ok {
|
||||
return false, p.ErrorValueTypeMismatch()
|
||||
}
|
||||
|
||||
if allowedValue == preferenceValue {
|
||||
isInAllowedValues = true
|
||||
}
|
||||
case PreferenceValueTypeBoolean:
|
||||
allowedValue, ok := value.(bool)
|
||||
if !ok {
|
||||
return false, p.ErrorValueTypeMismatch()
|
||||
}
|
||||
|
||||
if allowedValue == preferenceValue {
|
||||
isInAllowedValues = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return isInAllowedValues, nil
|
||||
}
|
||||
|
||||
func (p *Preference) IsValidValue(preferenceValue interface{}) *model.ApiError {
|
||||
typeSafeValue := preferenceValue
|
||||
switch p.ValueType {
|
||||
case PreferenceValueTypeInteger:
|
||||
val, ok := preferenceValue.(int64)
|
||||
if !ok {
|
||||
floatVal, ok := preferenceValue.(float64)
|
||||
if !ok || floatVal != float64(int64(floatVal)) {
|
||||
return p.ErrorValueTypeMismatch()
|
||||
}
|
||||
val = int64(floatVal)
|
||||
typeSafeValue = val
|
||||
}
|
||||
if !p.IsDiscreteValues {
|
||||
if val < p.Range.Min || val > p.Range.Max {
|
||||
return &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("the preference value is not in the range specified, min: %v , max:%v", p.Range.Min, p.Range.Max)}
|
||||
}
|
||||
}
|
||||
case PreferenceValueTypeString:
|
||||
_, ok := preferenceValue.(string)
|
||||
if !ok {
|
||||
return p.ErrorValueTypeMismatch()
|
||||
}
|
||||
case PreferenceValueTypeFloat:
|
||||
_, ok := preferenceValue.(float64)
|
||||
if !ok {
|
||||
return p.ErrorValueTypeMismatch()
|
||||
}
|
||||
case PreferenceValueTypeBoolean:
|
||||
_, ok := preferenceValue.(bool)
|
||||
if !ok {
|
||||
return p.ErrorValueTypeMismatch()
|
||||
}
|
||||
}
|
||||
|
||||
// check the validity of the value being part of allowed values or the range specified if any
|
||||
if p.IsDiscreteValues {
|
||||
if p.AllowedValues != nil {
|
||||
isInAllowedValues, valueMisMatchErr := p.checkIfInAllowedValues(typeSafeValue)
|
||||
|
||||
if valueMisMatchErr != nil {
|
||||
return valueMisMatchErr
|
||||
}
|
||||
if !isInAllowedValues {
|
||||
return &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("the preference value is not in the list of allowedValues: %v", p.AllowedValues)}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Preference) IsEnabledForScope(scope string) bool {
|
||||
isPreferenceEnabledForGivenScope := false
|
||||
if p.AllowedScopes != nil {
|
||||
for _, allowedScope := range p.AllowedScopes {
|
||||
if allowedScope == strings.ToLower(scope) {
|
||||
isPreferenceEnabledForGivenScope = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return isPreferenceEnabledForGivenScope
|
||||
}
|
||||
|
||||
func (p *Preference) SanitizeValue(preferenceValue interface{}) interface{} {
|
||||
switch p.ValueType {
|
||||
case PreferenceValueTypeBoolean:
|
||||
if preferenceValue == "1" || preferenceValue == true {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
return preferenceValue
|
||||
}
|
||||
}
|
||||
|
||||
type AllPreferences struct {
|
||||
Preference
|
||||
Value interface{} `json:"value"`
|
||||
}
|
||||
|
||||
type PreferenceKV struct {
|
||||
PreferenceId string `json:"preference_id" db:"preference_id"`
|
||||
PreferenceValue interface{} `json:"preference_value" db:"preference_value"`
|
||||
}
|
||||
|
||||
type UpdatePreference struct {
|
||||
PreferenceValue interface{} `json:"preference_value"`
|
||||
}
|
||||
|
||||
var db *sqlx.DB
|
||||
|
||||
func InitDB(datasourceName string) error {
|
||||
var err error
|
||||
db, err = sqlx.Open("sqlite3", datasourceName)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create the user preference table
|
||||
tableSchema := `
|
||||
PRAGMA foreign_keys = ON;
|
||||
CREATE TABLE IF NOT EXISTS user_preference(
|
||||
preference_id TEXT NOT NULL,
|
||||
preference_value TEXT,
|
||||
user_id TEXT NOT NULL,
|
||||
PRIMARY KEY (preference_id,user_id),
|
||||
FOREIGN KEY (user_id)
|
||||
REFERENCES users(id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE
|
||||
);`
|
||||
|
||||
_, err = db.Exec(tableSchema)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in creating user_preference table: %s", err.Error())
|
||||
}
|
||||
|
||||
// create the org preference table
|
||||
tableSchema = `
|
||||
PRAGMA foreign_keys = ON;
|
||||
CREATE TABLE IF NOT EXISTS org_preference(
|
||||
preference_id TEXT NOT NULL,
|
||||
preference_value TEXT,
|
||||
org_id TEXT NOT NULL,
|
||||
PRIMARY KEY (preference_id,org_id),
|
||||
FOREIGN KEY (org_id)
|
||||
REFERENCES organizations(id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE
|
||||
);`
|
||||
|
||||
_, err = db.Exec(tableSchema)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in creating org_preference table: %s", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// org preference functions
|
||||
func GetOrgPreference(ctx context.Context, preferenceId string, orgId string) (*PreferenceKV, *model.ApiError) {
|
||||
// check if the preference key exists or not
|
||||
preference, seen := preferenceMap[preferenceId]
|
||||
if !seen {
|
||||
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("no such preferenceId exists: %s", preferenceId)}
|
||||
}
|
||||
|
||||
// check if the preference is enabled for org scope or not
|
||||
isPreferenceEnabled := preference.IsEnabledForScope(OrgAllowedScope)
|
||||
if !isPreferenceEnabled {
|
||||
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("preference is not enabled at org scope: %s", preferenceId)}
|
||||
}
|
||||
|
||||
// fetch the value from the database
|
||||
var orgPreference PreferenceKV
|
||||
query := `SELECT preference_id , preference_value FROM org_preference WHERE preference_id=$1 AND org_id=$2;`
|
||||
err := db.Get(&orgPreference, query, preferenceId, orgId)
|
||||
|
||||
// if the value doesn't exist in db then return the default value
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return &PreferenceKV{
|
||||
PreferenceId: preferenceId,
|
||||
PreferenceValue: preference.DefaultValue,
|
||||
}, nil
|
||||
}
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in fetching the org preference: %s", err.Error())}
|
||||
|
||||
}
|
||||
|
||||
// else return the value fetched from the org_preference table
|
||||
return &PreferenceKV{
|
||||
PreferenceId: preferenceId,
|
||||
PreferenceValue: preference.SanitizeValue(orgPreference.PreferenceValue),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func UpdateOrgPreference(ctx context.Context, preferenceId string, preferenceValue interface{}, orgId string) (*PreferenceKV, *model.ApiError) {
|
||||
// check if the preference key exists or not
|
||||
preference, seen := preferenceMap[preferenceId]
|
||||
if !seen {
|
||||
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("no such preferenceId exists: %s", preferenceId)}
|
||||
}
|
||||
|
||||
// check if the preference is enabled at org scope or not
|
||||
isPreferenceEnabled := preference.IsEnabledForScope(OrgAllowedScope)
|
||||
if !isPreferenceEnabled {
|
||||
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("preference is not enabled at org scope: %s", preferenceId)}
|
||||
}
|
||||
|
||||
err := preference.IsValidValue(preferenceValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// update the values in the org_preference table and return the key and the value
|
||||
query := `INSERT INTO org_preference(preference_id,preference_value,org_id) VALUES($1,$2,$3)
|
||||
ON CONFLICT(preference_id,org_id) DO
|
||||
UPDATE SET preference_value= $2 WHERE preference_id=$1 AND org_id=$3;`
|
||||
|
||||
_, dberr := db.Exec(query, preferenceId, preferenceValue, orgId)
|
||||
|
||||
if dberr != nil {
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in setting the preference value: %s", dberr.Error())}
|
||||
}
|
||||
|
||||
return &PreferenceKV{
|
||||
PreferenceId: preferenceId,
|
||||
PreferenceValue: preferenceValue,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetAllOrgPreferences(ctx context.Context, orgId string) (*[]AllPreferences, *model.ApiError) {
|
||||
// filter out all the org enabled preferences from the preference variable
|
||||
allOrgPreferences := []AllPreferences{}
|
||||
|
||||
// fetch all the org preference values stored in org_preference table
|
||||
orgPreferenceValues := []PreferenceKV{}
|
||||
|
||||
query := `SELECT preference_id,preference_value FROM org_preference WHERE org_id=$1;`
|
||||
err := db.Select(&orgPreferenceValues, query, orgId)
|
||||
|
||||
if err != nil {
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in getting all org preference values: %s", err)}
|
||||
}
|
||||
|
||||
// create a map of key vs values from the above response
|
||||
preferenceValueMap := map[string]interface{}{}
|
||||
|
||||
for _, preferenceValue := range orgPreferenceValues {
|
||||
preferenceValueMap[preferenceValue.PreferenceId] = preferenceValue.PreferenceValue
|
||||
}
|
||||
|
||||
// update in the above filtered list wherver value present in the map
|
||||
for _, preference := range preferenceMap {
|
||||
isEnabledForOrgScope := preference.IsEnabledForScope(OrgAllowedScope)
|
||||
if isEnabledForOrgScope {
|
||||
preferenceWithValue := AllPreferences{}
|
||||
preferenceWithValue.Key = preference.Key
|
||||
preferenceWithValue.Name = preference.Name
|
||||
preferenceWithValue.Description = preference.Description
|
||||
preferenceWithValue.AllowedScopes = preference.AllowedScopes
|
||||
preferenceWithValue.AllowedValues = preference.AllowedValues
|
||||
preferenceWithValue.DefaultValue = preference.DefaultValue
|
||||
preferenceWithValue.Range = preference.Range
|
||||
preferenceWithValue.ValueType = preference.ValueType
|
||||
preferenceWithValue.IsDiscreteValues = preference.IsDiscreteValues
|
||||
value, seen := preferenceValueMap[preference.Key]
|
||||
|
||||
if seen {
|
||||
preferenceWithValue.Value = value
|
||||
} else {
|
||||
preferenceWithValue.Value = preference.DefaultValue
|
||||
}
|
||||
|
||||
preferenceWithValue.Value = preference.SanitizeValue(preferenceWithValue.Value)
|
||||
allOrgPreferences = append(allOrgPreferences, preferenceWithValue)
|
||||
}
|
||||
}
|
||||
return &allOrgPreferences, nil
|
||||
}
|
||||
|
||||
// user preference functions
|
||||
func GetUserPreference(ctx context.Context, preferenceId string, orgId string, userId string) (*PreferenceKV, *model.ApiError) {
|
||||
// check if the preference key exists
|
||||
preference, seen := preferenceMap[preferenceId]
|
||||
if !seen {
|
||||
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("no such preferenceId exists: %s", preferenceId)}
|
||||
}
|
||||
|
||||
preferenceValue := PreferenceKV{
|
||||
PreferenceId: preferenceId,
|
||||
PreferenceValue: preference.DefaultValue,
|
||||
}
|
||||
|
||||
// check if the preference is enabled at user scope
|
||||
isPreferenceEnabledAtUserScope := preference.IsEnabledForScope(UserAllowedScope)
|
||||
if !isPreferenceEnabledAtUserScope {
|
||||
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("preference is not enabled at user scope: %s", preferenceId)}
|
||||
}
|
||||
|
||||
isPreferenceEnabledAtOrgScope := preference.IsEnabledForScope(OrgAllowedScope)
|
||||
// get the value from the org scope if enabled at org scope
|
||||
if isPreferenceEnabledAtOrgScope {
|
||||
orgPreference := PreferenceKV{}
|
||||
|
||||
query := `SELECT preference_id , preference_value FROM org_preference WHERE preference_id=$1 AND org_id=$2;`
|
||||
|
||||
err := db.Get(&orgPreference, query, preferenceId, orgId)
|
||||
|
||||
// if there is error in getting values and its not an empty rows error return from here
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in getting org preference values: %s", err.Error())}
|
||||
}
|
||||
|
||||
// if there is no error update the preference value with value from org preference
|
||||
if err == nil {
|
||||
preferenceValue.PreferenceValue = orgPreference.PreferenceValue
|
||||
}
|
||||
}
|
||||
|
||||
// get the value from the user_preference table, if exists return this value else the one calculated in the above step
|
||||
userPreference := PreferenceKV{}
|
||||
|
||||
query := `SELECT preference_id, preference_value FROM user_preference WHERE preference_id=$1 AND user_id=$2;`
|
||||
err := db.Get(&userPreference, query, preferenceId, userId)
|
||||
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in getting user preference values: %s", err.Error())}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
preferenceValue.PreferenceValue = userPreference.PreferenceValue
|
||||
}
|
||||
|
||||
return &PreferenceKV{
|
||||
PreferenceId: preferenceValue.PreferenceId,
|
||||
PreferenceValue: preference.SanitizeValue(preferenceValue.PreferenceValue),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func UpdateUserPreference(ctx context.Context, preferenceId string, preferenceValue interface{}, userId string) (*PreferenceKV, *model.ApiError) {
|
||||
// check if the preference id is valid
|
||||
preference, seen := preferenceMap[preferenceId]
|
||||
if !seen {
|
||||
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("no such preferenceId exists: %s", preferenceId)}
|
||||
}
|
||||
|
||||
// check if the preference is enabled at user scope
|
||||
isPreferenceEnabledAtUserScope := preference.IsEnabledForScope(UserAllowedScope)
|
||||
if !isPreferenceEnabledAtUserScope {
|
||||
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("preference is not enabled at user scope: %s", preferenceId)}
|
||||
}
|
||||
|
||||
err := preference.IsValidValue(preferenceValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// update the user preference values
|
||||
query := `INSERT INTO user_preference(preference_id,preference_value,user_id) VALUES($1,$2,$3)
|
||||
ON CONFLICT(preference_id,user_id) DO
|
||||
UPDATE SET preference_value= $2 WHERE preference_id=$1 AND user_id=$3;`
|
||||
|
||||
_, dberrr := db.Exec(query, preferenceId, preferenceValue, userId)
|
||||
|
||||
if dberrr != nil {
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in setting the preference value: %s", dberrr.Error())}
|
||||
}
|
||||
|
||||
return &PreferenceKV{
|
||||
PreferenceId: preferenceId,
|
||||
PreferenceValue: preferenceValue,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetAllUserPreferences(ctx context.Context, orgId string, userId string) (*[]AllPreferences, *model.ApiError) {
|
||||
allUserPreferences := []AllPreferences{}
|
||||
|
||||
// fetch all the org preference values stored in org_preference table
|
||||
orgPreferenceValues := []PreferenceKV{}
|
||||
|
||||
query := `SELECT preference_id,preference_value FROM org_preference WHERE org_id=$1;`
|
||||
err := db.Select(&orgPreferenceValues, query, orgId)
|
||||
|
||||
if err != nil {
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in getting all org preference values: %s", err)}
|
||||
}
|
||||
|
||||
// create a map of key vs values from the above response
|
||||
preferenceOrgValueMap := map[string]interface{}{}
|
||||
|
||||
for _, preferenceValue := range orgPreferenceValues {
|
||||
preferenceOrgValueMap[preferenceValue.PreferenceId] = preferenceValue.PreferenceValue
|
||||
}
|
||||
|
||||
// fetch all the user preference values stored in user_preference table
|
||||
userPreferenceValues := []PreferenceKV{}
|
||||
|
||||
query = `SELECT preference_id,preference_value FROM user_preference WHERE user_id=$1;`
|
||||
err = db.Select(&userPreferenceValues, query, userId)
|
||||
|
||||
if err != nil {
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in getting all user preference values: %s", err)}
|
||||
}
|
||||
|
||||
// create a map of key vs values from the above response
|
||||
preferenceUserValueMap := map[string]interface{}{}
|
||||
|
||||
for _, preferenceValue := range userPreferenceValues {
|
||||
preferenceUserValueMap[preferenceValue.PreferenceId] = preferenceValue.PreferenceValue
|
||||
}
|
||||
|
||||
// update in the above filtered list wherver value present in the map
|
||||
for _, preference := range preferenceMap {
|
||||
isEnabledForUserScope := preference.IsEnabledForScope(UserAllowedScope)
|
||||
|
||||
if isEnabledForUserScope {
|
||||
preferenceWithValue := AllPreferences{}
|
||||
preferenceWithValue.Key = preference.Key
|
||||
preferenceWithValue.Name = preference.Name
|
||||
preferenceWithValue.Description = preference.Description
|
||||
preferenceWithValue.AllowedScopes = preference.AllowedScopes
|
||||
preferenceWithValue.AllowedValues = preference.AllowedValues
|
||||
preferenceWithValue.DefaultValue = preference.DefaultValue
|
||||
preferenceWithValue.Range = preference.Range
|
||||
preferenceWithValue.ValueType = preference.ValueType
|
||||
preferenceWithValue.IsDiscreteValues = preference.IsDiscreteValues
|
||||
preferenceWithValue.Value = preference.DefaultValue
|
||||
|
||||
isEnabledForOrgScope := preference.IsEnabledForScope(OrgAllowedScope)
|
||||
if isEnabledForOrgScope {
|
||||
value, seen := preferenceOrgValueMap[preference.Key]
|
||||
if seen {
|
||||
preferenceWithValue.Value = value
|
||||
}
|
||||
}
|
||||
|
||||
value, seen := preferenceUserValueMap[preference.Key]
|
||||
|
||||
if seen {
|
||||
preferenceWithValue.Value = value
|
||||
}
|
||||
|
||||
preferenceWithValue.Value = preference.SanitizeValue(preferenceWithValue.Value)
|
||||
allUserPreferences = append(allUserPreferences, preferenceWithValue)
|
||||
}
|
||||
}
|
||||
return &allUserPreferences, nil
|
||||
}
|
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@ -27,6 +28,7 @@ import (
|
||||
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
|
||||
"go.signoz.io/signoz/pkg/query-service/app/opamp"
|
||||
opAmpModel "go.signoz.io/signoz/pkg/query-service/app/opamp/model"
|
||||
"go.signoz.io/signoz/pkg/query-service/app/preferences"
|
||||
"go.signoz.io/signoz/pkg/query-service/common"
|
||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||
|
||||
@ -94,6 +96,10 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := preferences.InitDB(constants.RELATIONAL_DATASOURCE_PATH); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
localDB, err := dashboards.InitDB(constants.RELATIONAL_DATASOURCE_PATH)
|
||||
explorer.InitWithDSN(constants.RELATIONAL_DATASOURCE_PATH)
|
||||
|
||||
@ -268,7 +274,21 @@ func (s *Server) createPublicServer(api *APIHandler) (*http.Server, error) {
|
||||
r.Use(s.analyticsMiddleware)
|
||||
r.Use(loggingMiddleware)
|
||||
|
||||
am := NewAuthMiddleware(auth.GetUserFromRequest)
|
||||
// add auth middleware
|
||||
getUserFromRequest := func(r *http.Request) (*model.UserPayload, error) {
|
||||
user, err := auth.GetUserFromRequest(r)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if user.User.OrgId == "" {
|
||||
return nil, model.UnauthorizedError(errors.New("orgId is missing in the claims"))
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
am := NewAuthMiddleware(getUserFromRequest)
|
||||
|
||||
api.RegisterRoutes(r, am)
|
||||
api.RegisterLogsRoutes(r, am)
|
||||
|
@ -467,6 +467,10 @@ func authenticateLogin(ctx context.Context, req *model.LoginRequest) (*model.Use
|
||||
return nil, errors.Wrap(err, "failed to validate refresh token")
|
||||
}
|
||||
|
||||
if user.OrgId == "" {
|
||||
return nil, model.UnauthorizedError(errors.New("orgId is missing in the claims"))
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
@ -505,6 +509,7 @@ func GenerateJWTForUser(user *model.User) (model.UserJwtObject, error) {
|
||||
"gid": user.GroupId,
|
||||
"email": user.Email,
|
||||
"exp": j.AccessJwtExpiry,
|
||||
"orgId": user.OrgId,
|
||||
})
|
||||
|
||||
j.AccessJwt, err = token.SignedString([]byte(JwtSecret))
|
||||
@ -518,6 +523,7 @@ func GenerateJWTForUser(user *model.User) (model.UserJwtObject, error) {
|
||||
"gid": user.GroupId,
|
||||
"email": user.Email,
|
||||
"exp": j.RefreshJwtExpiry,
|
||||
"orgId": user.OrgId,
|
||||
})
|
||||
|
||||
j.RefreshJwt, err = token.SignedString([]byte(JwtSecret))
|
||||
|
@ -20,6 +20,8 @@ var (
|
||||
)
|
||||
|
||||
func ParseJWT(jwtStr string) (jwt.MapClaims, error) {
|
||||
// TODO[@vikrantgupta25] : to update this to the claims check function for better integrity of JWT
|
||||
// reference - https://pkg.go.dev/github.com/golang-jwt/jwt/v5#Parser.ParseWithClaims
|
||||
token, err := jwt.Parse(jwtStr, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, errors.Errorf("unknown signing algo: %v", token.Header["alg"])
|
||||
@ -35,6 +37,7 @@ func ParseJWT(jwtStr string) (jwt.MapClaims, error) {
|
||||
if !ok || !token.Valid {
|
||||
return nil, errors.Errorf("Not a valid jwt claim")
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
@ -47,11 +50,18 @@ func validateUser(tok string) (*model.UserPayload, error) {
|
||||
if !claims.VerifyExpiresAt(now, true) {
|
||||
return nil, model.ErrorTokenExpired
|
||||
}
|
||||
|
||||
var orgId string
|
||||
if claims["orgId"] != nil {
|
||||
orgId = claims["orgId"].(string)
|
||||
}
|
||||
|
||||
return &model.UserPayload{
|
||||
User: model.User{
|
||||
Id: claims["id"].(string),
|
||||
GroupId: claims["gid"].(string),
|
||||
Email: claims["email"].(string),
|
||||
OrgId: orgId,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user