routebuildermdl.go 10.6 KiB
Newer Older
Roshan Patil's avatar
Roshan Patil committed
package routebuildermdl

import (
	"encoding/json"
Roshan Patil's avatar
Roshan Patil committed
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"strings"
Roshan Patil's avatar
Roshan Patil committed
	"time"

	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/filemdl"

	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/constantmdl"

	version "github.com/hashicorp/go-version"
Roshan Patil's avatar
Roshan Patil committed
	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/statemdl"
Roshan Patil's avatar
Roshan Patil committed

	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/authmdl/roleenforcemdl"
	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/loggermdl"

	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/authmdl/jwtmdl"

	"github.com/tidwall/gjson"

	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/errormdl"
	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/servicebuildermdl"
Roshan Patil's avatar
Roshan Patil committed

	"github.com/gin-gonic/gin"
)

// Init routing init
func Init(o, r, c *gin.RouterGroup, JWTKey string) {
Roshan Patil's avatar
Roshan Patil committed
	o.POST("/mql", OpenHandler)
	o.POST("/mql/login", loginHandler)
	o.POST("/mql/state", statemdl.StateHandler)
Roshan Patil's avatar
Roshan Patil committed
	r.POST("/mql", RestrictedHandler)
	c.POST("/mql", RoleBasedHandler)
	jwtmdl.GlobalJWTKey = JWTKey
Roshan Patil's avatar
Roshan Patil committed
}

func setResponseHeader(serviceName string) responseData {
	rd := responseData{}
	val, ok := GetResponseHeader(serviceName)
	if ok {
		rd.ResponseHeader = val
	}
	return rd
}

func isMultipartRequest(header string) bool {
	return strings.HasPrefix(header, "multipart/form-data")
}

func executeService(name string, data []byte, isForm bool, formData *multipart.Form, isRestricted, isRoleBased bool, principalObj servicebuildermdl.Principal) (interface{}, interface{}, *servicebuildermdl.AbstractBusinessLogicHolder, bool, int, error) {
	var isCompressed bool
	var nextDynamicPage interface{}
Roshan Patil's avatar
Roshan Patil committed
	var service interface{}
	var ab *servicebuildermdl.AbstractBusinessLogicHolder
Roshan Patil's avatar
Roshan Patil committed
	var found bool
	if isRestricted {
		if isRoleBased {
			service, found = roleBasedServices.Get(name)
		} else {
			service, found = restrictedServices.Get(name)
		}
Roshan Patil's avatar
Roshan Patil committed
	} else {
		service, found = openServices.Get(name)
	}
	if !found {
		// ab.SetErrorCode(errormdl.SERVICENOTFOUND)
		loggermdl.LogError("Service Not Found: " + name)
		return nil, nextDynamicPage, ab, isCompressed, errormdl.SERVICENOTFOUND, errormdl.Wrap("Service Not Found: " + name)
Roshan Patil's avatar
Roshan Patil committed
	}
Roshan Patil's avatar
Roshan Patil committed
	var result interface{}
	var dynamicFormConfig gjson.Result
Roshan Patil's avatar
Roshan Patil committed
	var serviceError error
	start := time.Now()
	tmpServiceCache := service.(ServiceCache)
	serviceCache := tmpServiceCache
Roshan Patil's avatar
Roshan Patil committed
	if isForm {
		result, ab, serviceError = serviceCache.FormService(formData, principalObj)
Roshan Patil's avatar
Roshan Patil committed
	} else {
		if serviceCache.IsFormService {
			result, serviceError = nil, errormdl.Wrap("Form_Header_Missing")
Roshan Patil's avatar
Roshan Patil committed
			ab.SetErrorCode(errormdl.DATAINVALIDERROR)
			loggermdl.LogError("FORM_HEADER_MISSING")
Roshan Patil's avatar
Roshan Patil committed
		} else {
			if serviceCache.IsMasterService {
				result, serviceError = serviceCache.MasterService.Run(data, &principalObj)
			} else {
				rs := gjson.ParseBytes(data)
				if serviceCache.ServiceType == constantmdl.DYNAMICFORM {
					dynamicFormConfig = rs.Get(constantmdl.DynamicFormConfigKey)
					rs = rs.Get(constantmdl.DynamicFormPayLoadKey)
				}
				result, ab, serviceError = serviceCache.Service(&rs, principalObj)
Roshan Patil's avatar
Roshan Patil committed
		}
Roshan Patil's avatar
Roshan Patil committed
	}
Roshan Patil's avatar
Roshan Patil committed
	servingTime := time.Since(start)
	// Record State for every service
	go statemdl.UpdateServiceState(name, servingTime, serviceError, isRestricted, isRoleBased)

	if serviceError == nil {
		if serviceCache.ServiceType == constantmdl.HEAVYDATA {
			result, isCompressed = compressResponse(result)
		}
		if serviceCache.ServiceType == constantmdl.DYNAMICFORM {
			nextDynamicPage, serviceError = dynamicFormEvalution(ab, dynamicFormConfig)
		}
	}
	return result, nextDynamicPage, ab, isCompressed, errormdl.EXPECTATIONFAILED, serviceError
func commonHandler(c *gin.Context, isRestricted, isRoleBased bool, principalObj servicebuildermdl.Principal) {
	serviceHeader := c.Request.Header.Get("Service-Header")
	services := strings.Split(serviceHeader, ",")
Roshan Patil's avatar
Roshan Patil committed
	header := c.Request.Header.Get("Content-Type")
	versionError := appVersioning(c)
	if versionError != nil {
		c.JSON(http.StatusExpectationFailed, versionError.Error())
		return
	}
	responseMap := make(map[string]responseData)
Roshan Patil's avatar
Roshan Patil committed
	if isMultipartRequest(header) {
		responseDataObj := responseData{}
Roshan Patil's avatar
Roshan Patil committed
		form, multiPartError := c.MultipartForm()
		if errormdl.CheckErr(multiPartError) != nil {
			responseDataObj.Error = errormdl.CheckErr(multiPartError).Error()
			loggermdl.LogError(multiPartError)
Roshan Patil's avatar
Roshan Patil committed
			c.JSON(http.StatusExpectationFailed, responseDataObj)
			return
Roshan Patil's avatar
Roshan Patil committed
		}
		for i := 0; i < len(services); i++ {
			service := services[i]
			result, nextDynamicPage, ab, isCompressed, errorCode, err := executeService(service, nil, true, form, isRestricted, isRoleBased, principalObj)
			if errormdl.CheckErr1(err) != nil {
				responseDataObj.Error = errormdl.CheckErr1(err).Error()
				if ab == nil {
					responseDataObj.ErrorCode = errorCode

				} else {
					errorCode := ab.GetErrorCode()
					if errorCode == 0 {
						errorCode = errormdl.EXPECTATIONFAILED
					}
					responseDataObj.ErrorCode = errorCode
Roshan Patil's avatar
Roshan Patil committed
				}
Roshan Patil's avatar
Roshan Patil committed
			} else {
				responseDataObj.Result = result
				responseDataObj.ErrorCode = errormdl.NOERROR
			responseDataObj.NextDynamicPage = nextDynamicPage
			responseDataObj.IsCompressed = isCompressed
			responseDataObj = formatResponse(ab, responseDataObj)
			responseMap[service] = responseDataObj
Roshan Patil's avatar
Roshan Patil committed
		}

		c.JSON(http.StatusOK, responseMap)
Roshan Patil's avatar
Roshan Patil committed
		return
	}
	responseDataObj := responseData{}
	var reqBody []byte
	if c.Request.Body != nil {
		var readError error
		reqBody, readError = ioutil.ReadAll(c.Request.Body)
		if errormdl.CheckErr2(readError) != nil {
			responseDataObj.Error = errormdl.CheckErr2(readError).Error()
Roshan Patil's avatar
Roshan Patil committed
			responseDataObj.ErrorCode = errormdl.EXPECTATIONFAILED
			loggermdl.LogError(readError)
			c.JSON(http.StatusExpectationFailed, responseDataObj)
			return
		}
Roshan Patil's avatar
Roshan Patil committed
	}
	requestBody := gjson.ParseBytes(reqBody)
	for i := 0; i < len(services); i++ {
		responseDataObj := responseData{}
		service := services[i]
		result, nextDynamicPage, ab, isCompressed, errorCode, err := executeService(service, []byte(requestBody.Get(service).String()), false, nil, isRestricted, isRoleBased, principalObj)
		if errormdl.CheckErr3(err) != nil {
			responseDataObj.Error = errormdl.CheckErr3(err).Error()
			loggermdl.LogError(err)
			if ab == nil {
				responseDataObj.ErrorCode = errorCode

			} else {
				errorCode := ab.GetErrorCode()
				if errorCode == 0 {
					errorCode = errormdl.EXPECTATIONFAILED
				}
				responseDataObj.ErrorCode = errorCode
Roshan Patil's avatar
Roshan Patil committed
			}
Roshan Patil's avatar
Roshan Patil committed
		} else {
			responseDataObj.Result = result
			responseDataObj.ErrorCode = errormdl.NOERROR
		responseDataObj.NextDynamicPage = nextDynamicPage
		responseDataObj.IsCompressed = isCompressed
		responseDataObj = formatResponse(ab, responseDataObj)
		responseMap[service] = responseDataObj
Roshan Patil's avatar
Roshan Patil committed
	}
	c.JSON(http.StatusOK, responseMap)
// OpenHandler for /o
Roshan Patil's avatar
Roshan Patil committed
func OpenHandler(c *gin.Context) {
	commonHandler(c, false, false, servicebuildermdl.Principal{})
// RestrictedHandler for /r
Roshan Patil's avatar
Roshan Patil committed
func RestrictedHandler(c *gin.Context) {
	pricipalObj, extractError := extractPricipalObject(c)
	if extractError != nil {
		loggermdl.LogError(extractError)
		c.JSON(http.StatusExpectationFailed, extractError.Error())
		return
	}
	commonHandler(c, true, false, pricipalObj)
// RoleBasedHandler for /r/c
Roshan Patil's avatar
Roshan Patil committed
func RoleBasedHandler(c *gin.Context) {
	pricipalObj, extractError := extractPricipalObject(c)
	if extractError != nil {
		loggermdl.LogError(extractError)
		c.JSON(http.StatusExpectationFailed, extractError.Error())
		return
	}
	commonHandler(c, true, true, pricipalObj)
}
func extractPricipalObject(c *gin.Context) (servicebuildermdl.Principal, error) {
	principal := servicebuildermdl.Principal{}
	if jwtmdl.GlobalJWTKey == "" {
Roshan Patil's avatar
Roshan Patil committed
		return principal, errormdl.Wrap("No Global JWT key found")
	}
	claim, decodeError := jwtmdl.DecodeToken(c.Request)
	if errormdl.CheckErr(decodeError) != nil {
		loggermdl.LogError(decodeError)
		return principal, errormdl.CheckErr(decodeError)
	}

	groups, grperr := roleenforcemdl.GetGroupNames(claim, "groups")
	if errormdl.CheckErr(grperr) != nil {
		loggermdl.LogError(grperr)
		return principal, errormdl.CheckErr(grperr)
	}
	userID, ok := claim["userId"].(string)
	if !ok || len(userID) < 2 {
		loggermdl.LogError("Unable to parse UserID from JWT Token")
		return principal, errormdl.Wrap("Unable to parse UserID from JWT Token")
	}
	principal.Groups = groups
	principal.UserID = userID
	return principal, nil
Roshan Patil's avatar
Roshan Patil committed
}

// loginHandler - for specially login
func loginHandler(c *gin.Context) {
	if loginService == nil {
		loggermdl.LogError("NO Login Service found")
		c.JSON(http.StatusExpectationFailed, "NO Login Service found")
		return
	}
	var reqBody []byte
	if c.Request.Body != nil {
		var readError error
		reqBody, readError = ioutil.ReadAll(c.Request.Body)
		if errormdl.CheckErr2(readError) != nil {
			loggermdl.LogError(readError)
			c.JSON(http.StatusExpectationFailed, readError.Error())
			return
		}
	}
	rs := gjson.ParseBytes(reqBody)
	data, token, loginError := loginService(&rs, servicebuildermdl.Principal{})
	if errormdl.CheckErr(loginError) != nil {
		loggermdl.LogError(loginError)
		c.JSON(http.StatusExpectationFailed, errormdl.CheckErr(loginError).Error())
		return
	}
	c.Header("Authorization", token)
	c.JSON(http.StatusOK, data)
}

func appVersioning(c *gin.Context) error {
	if isAppVersionEnabled {
		appVersion := c.Request.Header.Get("app-version")
		if appVersion == "" {
			return errormdl.Wrap("No App version Found in request header")
		}
		ver, err := version.NewVersion(appVersion)
		if errormdl.CheckErr(err) != nil {
			return errormdl.CheckErr(err)
		}
		if isStrictMode {
			if !ver.Equal(applicationVersion) {
				return errormdl.Wrap("Application version mismatched")
			}
		} else {
			if ver.GreaterThan(applicationVersion) {
				return errormdl.Wrap("Server Version is outdated")
			}
			if ver.LessThan(minimumSupportedVersion) {
				return errormdl.Wrap("Client Version is outdated")
			}
		}
	}
	return nil
}

func compressResponse(result interface{}) (interface{}, bool) {
	switch result.(type) {
	case map[string]interface{}:
		{
			ba, err := json.Marshal(result)
			if errormdl.CheckErr(err) != nil {
				loggermdl.LogError(err)
				return result, false
			}
			if len(ba) < constantmdl.ResponseSizeThreshold {
				return result, false
			}
			ba, err = filemdl.ZipBytes(ba)
			if errormdl.CheckErr(err) != nil {
				loggermdl.LogError(err)
				return ba, false
			}
			return ba, true

		}
	case string:
		{
			str := result.(string)
			strBa := []byte(str)
			if len(strBa) < constantmdl.ResponseSizeThreshold {
				return result, false
			}
			ba, err := filemdl.ZipBytes(strBa)
			if errormdl.CheckErr(err) != nil {
				loggermdl.LogError(err)
				return ba, false
			}
			return ba, true
		}
	default:
		{
			return result, false
		}
	}
	return result, false
}