Copyright 2020 Red Hat, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
|
package server
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"regexp"
"strconv"
"strings"
httputils "github.com/RedHatInsights/insights-operator-utils/http"
ctypes "github.com/RedHatInsights/insights-results-types"
"github.com/rs/zerolog/log"
"github.com/RedHatInsights/insights-results-smart-proxy/types"
)
const (
|
OSDEligibleParam parameter
|
OSDEligibleParam = "osd_eligible"
|
GetDisabledParam parameter
|
GetDisabledParam = "get_disabled"
|
ImpactingParam parameter used to show/hide recommendations not hitting any clusters
|
ImpactingParam = "impacting"
|
RuleIDParamName parameter name in the URL
|
RuleIDParamName = "rule_id"
|
RequestIDParam parameter name in the URL for request IDs
|
RequestIDParam = "request_id"
|
NamespaceIDParam parameter name in the URL for namespace UUIDs
|
NamespaceIDParam = "namespace"
)
func readRuleIDWithErrorKey ( writer http . ResponseWriter , request * http . Request ) ( ctypes . RuleID , ctypes . ErrorKey , error ) {
ruleIDWithErrorKey , err := httputils . GetRouterParam ( request , RuleIDParamName )
if err != nil {
const message = "unable to get rule id"
log . Error ( ) . Err ( err ) . Msg ( message )
handleServerError ( writer , err )
return ctypes . RuleID ( "" ) , ctypes . ErrorKey ( "" ) , err
}
ruleID , errorKey , err := types . RuleIDWithErrorKeyFromCompositeRuleID ( ctypes . RuleID ( ruleIDWithErrorKey ) )
if err != nil {
handleServerError ( writer , & RouterParsingError {
ParamName : RuleIDParamName ,
ParamValue : ruleIDWithErrorKey ,
ErrString : err . Error ( ) ,
} )
return ctypes . RuleID ( "" ) , ctypes . ErrorKey ( "" ) , err
}
return ruleID , errorKey , nil
}
func readCompositeRuleID ( request * http . Request ) (
ruleID ctypes . RuleID ,
err error ,
) {
ruleIDParam , err := httputils . GetRouterParam ( request , RuleIDParamName )
if err != nil {
const message = "unable to get rule id"
log . Warn ( ) . Err ( err ) . Msg ( message )
return
}
compositeRuleIDValidator := regexp . MustCompile ( `^([a-zA-Z_0-9.]+)[|]([a-zA-Z_0-9.]+)$` )
isCompositeRuleIDValid := compositeRuleIDValidator . MatchString ( ruleIDParam )
if ! isCompositeRuleIDValid {
msg := fmt . Errorf ( "invalid composite rule ID. Must be in the format 'rule.plugin.module|ERROR_KEY'" )
err = & RouterParsingError {
ParamName : RuleIDParamName ,
ParamValue : ruleIDParam ,
ErrString : msg . Error ( ) ,
}
log . Error ( ) . Err ( err ) . Msg ( "invalid composite rule ID" )
return
}
ruleID = ctypes . RuleID ( ruleIDParam )
return
}
func ( server * HTTPServer ) readParamsGetRecommendations ( writer http . ResponseWriter , request * http . Request ) (
userID ctypes . UserID ,
orgID ctypes . OrgID ,
impactingFlag types . ImpactingFlag ,
err error ,
) {
orgID , userID , err = server . GetCurrentOrgIDUserIDFromToken ( request )
if err != nil {
log . Err ( err ) . Msg ( orgIDTokenError )
handleServerError ( writer , err )
return
}
impactingParam := request . URL . Query ( ) . Get ( ImpactingParam )
if impactingParam == "" {
|
impacting control flag is missing, display all recommendations
|
impactingFlag = IncludingImpacting
return
}
impactingParamBool , err := readImpactingParam ( request )
if err != nil {
log . Warn ( ) . Err ( err ) . Msgf ( "Error parsing `%s` URL parameter." , ImpactingParam )
handleServerError ( writer , & RouterParsingError {
ParamName : ImpactingParam ,
ErrString : "Unparsable boolean value" ,
} )
return
}
if impactingParamBool {
|
param impacting=true means to only include impacting recommendations
|
impactingFlag = OnlyImpacting
} else {
|
param impacting=false means to return all rules that aren't impacting any clusters
|
impactingFlag = ExcludingImpacting
}
return
}
|
readQueryParam return the value of the parameter in the query. If not found, defaults to false
|
func readQueryBoolParam ( name string , defaultValue bool , request * http . Request ) ( bool , error ) {
value := request . URL . Query ( ) . Get ( name )
if value == "" {
return defaultValue , nil
}
return strconv . ParseBool ( value )
}
|
readGetDisabledParam returns the value of the "get_disabled" parameter in query
if available
|
func readGetDisabledParam ( request * http . Request ) ( bool , error ) {
return readQueryBoolParam ( GetDisabledParam , false , request )
}
|
readOSDEligibleParam returns the value of the "osd_eligible" parameter in query
if available
|
func readOSDEligible ( request * http . Request ) ( bool , error ) {
return readQueryBoolParam ( OSDEligibleParam , false , request )
}
|
readImpactingParam returns the value of the "impacting" parameter in query if available
|
func readImpactingParam ( request * http . Request ) ( bool , error ) {
return readQueryBoolParam ( ImpactingParam , true , request )
}
|
readUserAgentHeaderProduct returns the produt part of the standard User Agent syntax
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent#syntax
|
func readUserAgentHeaderProduct ( request * http . Request ) ( userAgentProduct string ) {
userAgent := request . Header . Get ( userAgentHeader )
if userAgent == "" {
return
}
userAgentSplit := strings . Split ( userAgent , "/" )
|
we're only interested in the product name
|
userAgentProduct = userAgentSplit [ 0 ]
return
}
|
ValidateRequestID checks that the request ID has proper format.
Converted request ID is returned if everything is okay, otherwise an error is returned.
|
func ValidateRequestID ( requestID string ) ( types . RequestID , error ) {
IDValidator := regexp . MustCompile ( `^[a-zA-Z0-9]+$` )
if ! IDValidator . MatchString ( requestID ) {
message := fmt . Sprintf ( "invalid request ID: '%s'" , requestID )
err := errors . New ( message )
log . Error ( ) . Err ( err ) . Msg ( message )
return "" , err
}
return types . RequestID ( requestID ) , nil
}
|
readRequestID retrieves request ID from request
if it's not possible, it writes http error to the writer and returns error
|
func readRequestID ( writer http . ResponseWriter , request * http . Request ) ( types . RequestID , error ) {
requestID , err := httputils . GetRouterParam ( request , RequestIDParam )
if err != nil {
handleServerError ( writer , err )
return "" , err
}
validatedRequestID , err := ValidateRequestID ( requestID )
if err != nil {
err := & RouterParsingError {
ParamName : RequestIDParam ,
ParamValue : requestID ,
ErrString : err . Error ( ) ,
}
handleServerError ( writer , err )
return "" , err
}
return validatedRequestID , nil
}
func readRequestIDList ( writer http . ResponseWriter , request * http . Request ) (
[ ] types . RequestID , error ,
) {
var requestList [ ] string
|
check if there's any body provided in the request sent by client
|
if request . ContentLength <= 0 {
err := & NoBodyError { }
handleServerError ( writer , err )
return nil , err
}
err := json . NewDecoder ( request . Body ) . Decode ( & requestList )
if err != nil {
log . Error ( ) . Err ( err ) . Msg ( "unable to retrieve request ID list from request body" )
err := & BadBodyContent { }
handleServerError ( writer , err )
return nil , err
}
validatedRequestList := make ( [ ] types . RequestID , len ( requestList ) )
for i , requestID := range requestList {
validatedRequestID , err := ValidateRequestID ( requestID )
if err != nil {
err := & RouterParsingError {
ParamName : RequestIDParam ,
ParamValue : requestID ,
ErrString : err . Error ( ) ,
}
handleServerError ( writer , err )
return nil , err
}
validatedRequestList [ i ] = validatedRequestID
}
return validatedRequestList , nil
}
|
readNamespace retrieves namespace UUID from request
if it's not possible, it writes http error to the writer and returns error
|
func readNamespace ( writer http . ResponseWriter , request * http . Request ) (
namespace types . Namespace , err error ,
) {
namespaceID , err := httputils . GetRouterParam ( request , NamespaceIDParam )
if err != nil {
handleServerError ( writer , err )
return
}
validatedNamespaceID , err := validateNamespaceID ( namespaceID )
if err != nil {
err = & RouterParsingError {
ParamName : NamespaceIDParam ,
ParamValue : namespaceID ,
ErrString : err . Error ( ) ,
}
handleServerError ( writer , err )
return
}
namespace . UUID = validatedNamespaceID
return
}
|
rule tests used by molodec use non-UUID namespace IDs, we must allow any garbage
until that's resolved
|
func validateNamespaceID ( namespace string ) ( string , error ) {
IDValidator := regexp . MustCompile ( `^.{1,256}$` )
if ! IDValidator . MatchString ( namespace ) {
message := fmt . Sprintf ( "invalid namespace ID: '%s'" , namespace )
err := errors . New ( message )
log . Error ( ) . Err ( err ) . Msg ( message )
return "" , err
}
return namespace , nil
}
|