docxTemplater/middleware/middleware.go

280 lines
7.2 KiB
Go

package middleware
import (
"context"
"encoding/json"
"errors"
"net/http"
"strconv"
"strings"
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/backend/penahub_common/jwt_adapter"
"penahub.gitlab.yandexcloud.net/backend/templategen/amo"
"penahub.gitlab.yandexcloud.net/backend/templategen/dal"
"penahub.gitlab.yandexcloud.net/backend/templategen/dal/model"
)
type Middleware struct {
dal *dal.MongoDAL
amo *amo.ClientApp
logger *zap.Logger
AmoAccessMap map[string]string // key - endpoint; value - access (visibility, creation, delete)
}
type contextKey uint
const (
PenaUserIDContextKey contextKey = 1 // UserID from Pena JWT
AmoDataContextKey contextKey = 2 // AmoData
AmoIsAdminContextKey contextKey = 3 // Is Admin?
AmoUserIDContextKey contextKey = 4 // ID of user making the request from AMO CRM
)
func InitMiddleware(dal *dal.MongoDAL, amo *amo.ClientApp, logger *zap.Logger,
amoAccessMap map[string]string) *Middleware {
return &Middleware{dal: dal, amo: amo, logger: logger, AmoAccessMap: amoAccessMap}
}
func (mw *Middleware) MiddlewareCors(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
} else {
next.ServeHTTP(w, r)
}
})
}
func (mw *Middleware) Logger(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.String(), "/assets/") {
next.ServeHTTP(w, r)
return
}
var penaID string
amoData := GetAmoData(r)
if amoData != nil {
penaID = amoData.PenaID
}
mw.logger.Info("HttpRequest",
zap.String("URL", r.URL.String()),
zap.String("PenaID", penaID),
)
next.ServeHTTP(w, r)
})
}
func (mw *Middleware) MiddlewareJwt(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.String(), "/assets/") {
next.ServeHTTP(w, r)
return
}
ctx := r.Context()
// Если уже прошла авторизация через сервис AMO, то пропускаем дальнейшие действия
amoData := GetAmoData(r)
if amoData != nil {
ctx = context.WithValue(ctx, PenaUserIDContextKey, amoData.PenaID)
next.ServeHTTP(w, r.WithContext(ctx))
return
}
// Check token in headers
headerValue := r.Header.Get(jwt_adapter.DefaultHeaderKey)
jwt, err := jwt_adapter.Decode(headerValue)
if err != nil {
mw.reportError(w, http.StatusUnauthorized, err)
}
ctx = context.WithValue(ctx, PenaUserIDContextKey, jwt.GetUserID())
// Add AMO data to context
amoData, err = mw.dal.Amo.GetByPenaID(ctx, jwt.GetUserID())
if err != nil {
mw.reportError(w, http.StatusInternalServerError, err)
return
}
ctx = context.WithValue(ctx, AmoDataContextKey, amoData)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func (mw *Middleware) MiddlewareHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With, X-Auth-Token")
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Expose-Headers", "*")
w.Header().Set("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE")
next.ServeHTTP(w, r)
})
}
func (mw *Middleware) MiddlewareAmoJwt(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.String(), "/assets/") {
next.ServeHTTP(w, r)
return
}
authMap := map[string]interface{}{
// "/amo": nil,
}
required := false
_, ok := authMap[r.URL.String()]
if ok {
required = true
}
token, err := mw.amo.DecodeJwt(r)
if err != nil {
if required {
mw.reportError(w, http.StatusUnauthorized, err)
} else {
next.ServeHTTP(w, r)
}
return
}
data, err := mw.dal.Amo.GetByAccountID(r.Context(), strconv.FormatInt(token.AccountID, 10))
if err != nil {
mw.reportError(w, http.StatusInternalServerError, err)
return
}
if data == nil && !strings.Contains(r.URL.String(), "/amo/state") {
mw.reportError(w, http.StatusUnauthorized, errors.New("amo account not found"))
return
}
// check access rules
isAcessGranted := token.IsAdmin
// Если права не указаны для эндпоинта, то доступно любому юзеру
rule, ok := mw.AmoAccessMap[r.URL.String()]
if ok {
// Если не админ, то проверяем права пользователя
if rule == "visibility" && !isAcessGranted {
for _, userID := range data.AccessRules.Visibility {
if userID == token.UserID {
isAcessGranted = true
break
}
}
}
if rule == "creation" && !isAcessGranted {
for _, userID := range data.AccessRules.Creation {
if userID == token.UserID {
isAcessGranted = true
break
}
}
}
if rule == "delete" && !isAcessGranted {
for _, userID := range data.AccessRules.Delete {
if userID == token.UserID {
isAcessGranted = true
break
}
}
}
} else {
isAcessGranted = true
}
// Если прав нет, прерываемся
if !isAcessGranted {
mw.reportError(w, http.StatusForbidden, errors.New("amoUser forbidden")) // mb: http.StatusForbidden
return
}
ctx := context.WithValue(r.Context(), AmoDataContextKey, data)
ctx = context.WithValue(ctx, AmoIsAdminContextKey, token.IsAdmin)
ctx = context.WithValue(ctx, AmoUserIDContextKey, token.UserID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func (mw *Middleware) MiddlewareAmoPlug(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.String(), "/assets/") {
next.ServeHTTP(w, r)
return
}
ctx := r.Context()
if r.Header.Get("TestAuth") == "75bc853ae763ffc36cad97069682fed1" {
data, err := mw.dal.Amo.GetByAccountID(r.Context(), "30228997")
if err != nil {
mw.reportError(w, http.StatusInternalServerError, err)
return
}
if data == nil {
mw.reportError(w, http.StatusUnauthorized, errors.New("amo account not found"))
return
}
ctx = context.WithValue(ctx, AmoDataContextKey, data)
}
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func GetPenaUserID(r *http.Request) string {
user, ok := r.Context().Value(PenaUserIDContextKey).(string)
if ok {
return user
}
return ""
}
func GetAmoData(r *http.Request) *model.Amo {
amoData, ok := r.Context().Value(AmoDataContextKey).(*model.Amo)
if ok {
return amoData
}
return nil
}
func GetAmoUserID(r *http.Request) int64 {
result, ok := r.Context().Value(AmoUserIDContextKey).(int64)
if ok {
return result
}
return 0
}
func (mw *Middleware) reportError(w http.ResponseWriter, status int, err error) {
mw.logger.Error("ErrorMiddleware", zap.Error(err))
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
e := json.NewEncoder(w)
e.SetEscapeHTML(false)
errJSON := e.Encode(err)
if errJSON != nil {
mw.logger.Error("ErrorMiddleware - JSON ENCODE", zap.Error(errJSON))
}
}