mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 16:58:59 +08:00
fix(auth): Return 403 for forbidden requests due to rbac (#1060)
* Return error json for user not found * Return 403 for rbac error * Return not_found instead of internal_error for getInvite
This commit is contained in:
parent
5b316afa12
commit
6486425f46
@ -142,6 +142,8 @@ func respondError(w http.ResponseWriter, apiErr *model.ApiError, data interface{
|
|||||||
code = http.StatusNotImplemented
|
code = http.StatusNotImplemented
|
||||||
case model.ErrorUnauthorized:
|
case model.ErrorUnauthorized:
|
||||||
code = http.StatusUnauthorized
|
code = http.StatusUnauthorized
|
||||||
|
case model.ErrorForbidden:
|
||||||
|
code = http.StatusForbidden
|
||||||
default:
|
default:
|
||||||
code = http.StatusInternalServerError
|
code = http.StatusInternalServerError
|
||||||
}
|
}
|
||||||
@ -191,10 +193,19 @@ func OpenAccess(f func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
|||||||
|
|
||||||
func ViewAccess(f func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
func ViewAccess(f func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
if !(auth.IsViewer(r) || auth.IsEditor(r) || auth.IsAdmin(r)) {
|
user, err := auth.GetUserFromRequest(r)
|
||||||
|
if err != nil {
|
||||||
respondError(w, &model.ApiError{
|
respondError(w, &model.ApiError{
|
||||||
Typ: model.ErrorUnauthorized,
|
Typ: model.ErrorUnauthorized,
|
||||||
Err: errors.New("API accessible only to the admins"),
|
Err: err,
|
||||||
|
}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(auth.IsViewer(user) || auth.IsEditor(user) || auth.IsAdmin(user)) {
|
||||||
|
respondError(w, &model.ApiError{
|
||||||
|
Typ: model.ErrorForbidden,
|
||||||
|
Err: errors.New("API is not accessible to the viewers."),
|
||||||
}, nil)
|
}, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -204,10 +215,18 @@ func ViewAccess(f func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
|||||||
|
|
||||||
func EditAccess(f func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
func EditAccess(f func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
if !(auth.IsEditor(r) || auth.IsAdmin(r)) {
|
user, err := auth.GetUserFromRequest(r)
|
||||||
|
if err != nil {
|
||||||
respondError(w, &model.ApiError{
|
respondError(w, &model.ApiError{
|
||||||
Typ: model.ErrorUnauthorized,
|
Typ: model.ErrorUnauthorized,
|
||||||
Err: errors.New("API accessible only to the editors"),
|
Err: err,
|
||||||
|
}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !(auth.IsEditor(user) || auth.IsAdmin(user)) {
|
||||||
|
respondError(w, &model.ApiError{
|
||||||
|
Typ: model.ErrorForbidden,
|
||||||
|
Err: errors.New("API is not accessible to the editors."),
|
||||||
}, nil)
|
}, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -217,10 +236,19 @@ func EditAccess(f func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
|||||||
|
|
||||||
func SelfAccess(f func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
func SelfAccess(f func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
if !(auth.IsSelfAccessRequest(r) || auth.IsAdmin(r)) {
|
user, err := auth.GetUserFromRequest(r)
|
||||||
|
if err != nil {
|
||||||
respondError(w, &model.ApiError{
|
respondError(w, &model.ApiError{
|
||||||
Typ: model.ErrorUnauthorized,
|
Typ: model.ErrorUnauthorized,
|
||||||
Err: errors.New("API accessible only for self userId"),
|
Err: err,
|
||||||
|
}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id := mux.Vars(r)["id"]
|
||||||
|
if !(auth.IsSelfAccessRequest(user, id) || auth.IsAdmin(user)) {
|
||||||
|
respondError(w, &model.ApiError{
|
||||||
|
Typ: model.ErrorForbidden,
|
||||||
|
Err: errors.New("API accessible only for self userId or admins."),
|
||||||
}, nil)
|
}, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -230,9 +258,17 @@ func SelfAccess(f func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
|||||||
|
|
||||||
func AdminAccess(f func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
func AdminAccess(f func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
if !auth.IsAdmin(r) {
|
user, err := auth.GetUserFromRequest(r)
|
||||||
|
if err != nil {
|
||||||
respondError(w, &model.ApiError{
|
respondError(w, &model.ApiError{
|
||||||
Typ: model.ErrorUnauthorized,
|
Typ: model.ErrorUnauthorized,
|
||||||
|
Err: err,
|
||||||
|
}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !auth.IsAdmin(user) {
|
||||||
|
respondError(w, &model.ApiError{
|
||||||
|
Typ: model.ErrorForbidden,
|
||||||
Err: errors.New("API accessible only to the admins"),
|
Err: errors.New("API accessible only to the admins"),
|
||||||
}, nil)
|
}, nil)
|
||||||
return
|
return
|
||||||
@ -1163,7 +1199,7 @@ func (aH *APIHandler) getInvite(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
resp, err := auth.GetInvite(context.Background(), token)
|
resp, err := auth.GetInvite(context.Background(), token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(w, &model.ApiError{Err: err, Typ: model.ErrorInternal}, nil)
|
respondError(w, &model.ApiError{Err: err, Typ: model.ErrorNotFound}, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
aH.writeJSON(w, r, resp)
|
aH.writeJSON(w, r, resp)
|
||||||
@ -1268,8 +1304,8 @@ func (aH *APIHandler) listUsers(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// mask the password hash
|
// mask the password hash
|
||||||
for _, u := range users {
|
for i := range users {
|
||||||
u.Password = ""
|
users[i].Password = ""
|
||||||
}
|
}
|
||||||
aH.writeJSON(w, r, users)
|
aH.writeJSON(w, r, users)
|
||||||
}
|
}
|
||||||
@ -1284,11 +1320,16 @@ func (aH *APIHandler) getUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
respondError(w, err, "Failed to get user")
|
respondError(w, err, "Failed to get user")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// No need to send password hash for the user object.
|
if user == nil {
|
||||||
if user != nil {
|
respondError(w, &model.ApiError{
|
||||||
user.Password = ""
|
Typ: model.ErrorInternal,
|
||||||
|
Err: errors.New("User not found"),
|
||||||
|
}, nil)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No need to send password hash for the user object.
|
||||||
|
user.Password = ""
|
||||||
aH.writeJSON(w, r, user)
|
aH.writeJSON(w, r, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1336,14 +1377,6 @@ func (aH *APIHandler) editUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (aH *APIHandler) deleteUser(w http.ResponseWriter, r *http.Request) {
|
func (aH *APIHandler) deleteUser(w http.ResponseWriter, r *http.Request) {
|
||||||
id := mux.Vars(r)["id"]
|
id := mux.Vars(r)["id"]
|
||||||
|
|
||||||
if !auth.IsAdmin(r) {
|
|
||||||
respondError(w, &model.ApiError{
|
|
||||||
Typ: model.ErrorUnauthorized,
|
|
||||||
Err: errors.New("Only admin can delete user"),
|
|
||||||
}, "Failed to get user")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query for the user's group, and the admin's group. If the user belongs to the admin group
|
// Query for the user's group, and the admin's group. If the user belongs to the admin group
|
||||||
// and is the last user then don't let the deletion happen. Otherwise, the system will become
|
// and is the last user then don't let the deletion happen. Otherwise, the system will become
|
||||||
// admin less and hence inaccessible.
|
// admin less and hence inaccessible.
|
||||||
@ -1353,6 +1386,15 @@ func (aH *APIHandler) deleteUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
respondError(w, apiErr, "Failed to get user's group")
|
respondError(w, apiErr, "Failed to get user's group")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
respondError(w, &model.ApiError{
|
||||||
|
Typ: model.ErrorNotFound,
|
||||||
|
Err: errors.New("User not found"),
|
||||||
|
}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
adminGroup, apiErr := dao.DB().GetGroupByName(ctx, constants.AdminGroup)
|
adminGroup, apiErr := dao.DB().GetGroupByName(ctx, constants.AdminGroup)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
respondError(w, apiErr, "Failed to get admin group")
|
respondError(w, apiErr, "Failed to get admin group")
|
||||||
@ -1504,8 +1546,8 @@ func (aH *APIHandler) getOrgUsers(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// mask the password hash
|
// mask the password hash
|
||||||
for _, u := range users {
|
for i := range users {
|
||||||
u.Password = ""
|
users[i].Password = ""
|
||||||
}
|
}
|
||||||
aH.writeJSON(w, r, users)
|
aH.writeJSON(w, r, users)
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ func validateUser(tok string) (*model.UserPayload, error) {
|
|||||||
}
|
}
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
if !claims.VerifyExpiresAt(now, true) {
|
if !claims.VerifyExpiresAt(now, true) {
|
||||||
return nil, errors.Errorf("Token is expired")
|
return nil, model.ErrorTokenExpired
|
||||||
}
|
}
|
||||||
return &model.UserPayload{
|
return &model.UserPayload{
|
||||||
User: model.User{
|
User: model.User{
|
||||||
|
@ -6,11 +6,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"go.signoz.io/query-service/constants"
|
"go.signoz.io/query-service/constants"
|
||||||
"go.signoz.io/query-service/dao"
|
"go.signoz.io/query-service/dao"
|
||||||
"go.uber.org/zap"
|
"go.signoz.io/query-service/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Group struct {
|
type Group struct {
|
||||||
@ -51,65 +50,24 @@ func InitAuthCache(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsAdmin(r *http.Request) bool {
|
func GetUserFromRequest(r *http.Request) (*model.UserPayload, error) {
|
||||||
accessJwt, err := ExtractJwtFromRequest(r)
|
accessJwt, err := ExtractJwtFromRequest(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.S().Debugf("Failed to verify admin access, err: %v", err)
|
return nil, err
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := validateUser(accessJwt)
|
user, err := validateUser(accessJwt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return nil, err
|
||||||
}
|
}
|
||||||
return user.GroupId == AuthCacheObj.AdminGroupId
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsSelfAccessRequest(r *http.Request) bool {
|
func IsSelfAccessRequest(user *model.UserPayload, id string) bool { return user.Id == id }
|
||||||
id := mux.Vars(r)["id"]
|
|
||||||
accessJwt, err := ExtractJwtFromRequest(r)
|
|
||||||
if err != nil {
|
|
||||||
zap.S().Debugf("Failed to verify self access, err: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := validateUser(accessJwt)
|
func IsViewer(user *model.UserPayload) bool { return user.GroupId == AuthCacheObj.ViewerGroupId }
|
||||||
if err != nil {
|
func IsEditor(user *model.UserPayload) bool { return user.GroupId == AuthCacheObj.EditorGroupId }
|
||||||
zap.S().Debugf("Failed to verify self access, err: %v", err)
|
func IsAdmin(user *model.UserPayload) bool { return user.GroupId == AuthCacheObj.AdminGroupId }
|
||||||
return false
|
|
||||||
}
|
|
||||||
zap.S().Debugf("Self access verification, userID: %s, id: %s\n", user.Id, id)
|
|
||||||
return user.Id == id
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsViewer(r *http.Request) bool {
|
|
||||||
accessJwt, err := ExtractJwtFromRequest(r)
|
|
||||||
if err != nil {
|
|
||||||
zap.S().Debugf("Failed to verify viewer access, err: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := validateUser(accessJwt)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return user.GroupId == AuthCacheObj.ViewerGroupId
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsEditor(r *http.Request) bool {
|
|
||||||
accessJwt, err := ExtractJwtFromRequest(r)
|
|
||||||
if err != nil {
|
|
||||||
zap.S().Debugf("Failed to verify editor access, err: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := validateUser(accessJwt)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return user.GroupId == AuthCacheObj.EditorGroupId
|
|
||||||
}
|
|
||||||
|
|
||||||
func ValidatePassword(password string) error {
|
func ValidatePassword(password string) error {
|
||||||
if len(password) < minimumPasswordLength {
|
if len(password) < minimumPasswordLength {
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
|
import "github.com/pkg/errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrorTokenExpired = errors.New("Token is expired")
|
||||||
|
)
|
||||||
|
|
||||||
type InviteRequest struct {
|
type InviteRequest struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
@ -28,6 +28,7 @@ const (
|
|||||||
ErrorNotFound ErrorType = "not_found"
|
ErrorNotFound ErrorType = "not_found"
|
||||||
ErrorNotImplemented ErrorType = "not_implemented"
|
ErrorNotImplemented ErrorType = "not_implemented"
|
||||||
ErrorUnauthorized ErrorType = "unauthorized"
|
ErrorUnauthorized ErrorType = "unauthorized"
|
||||||
|
ErrorForbidden ErrorType = "forbidden"
|
||||||
)
|
)
|
||||||
|
|
||||||
type QueryDataV2 struct {
|
type QueryDataV2 struct {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user