Skip to content

Commit

Permalink
Allow setting of a warning header when changing the request body.
Browse files Browse the repository at this point in the history
See RFC 7234 for description of warning header.
We add a new step "SetHeaderAlways" to implement this.
  • Loading branch information
Jorge L. Williams committed Aug 3, 2016
1 parent 39b80ff commit 572ed73
Show file tree
Hide file tree
Showing 19 changed files with 906 additions and 235 deletions.
27 changes: 26 additions & 1 deletion TODO.org
Original file line number Diff line number Diff line change
Expand Up @@ -769,10 +769,35 @@
4. [X] Validator
5. [X] capture header
6. [X] rax:roles and rax:roles masked with other header_all
** TODO Document Breaking Changes [3/3]
** DONE Document Breaking Changes [3/3]
1. [X] Use of repeating with headers
2. [X] Use of header_all and header_any
3. [X] capture_header and message and code changes
** DONE SetHeaderAlways Step and introduce WARNING headers [3/3]
1. [X] SetHeaderAlways [5/5]
1. [X] Extend XSD to support SetHeaderAlways
2. [X] Checker assertions related to set header always (not needed)
3. [X] Ensure that optimizations work with header always
4. [X] Create new step
5. [X] Update handler for new step
2. [X] Warning Headers [4/4]
1. [X] Add Warning header config options
2. [X] Update builder to add new Warning headers
3. [X] Update CLI tools to support new config options
4. [X] Ensure that wadl2dot correctly lists set header always
3. [X] Testing [5/5]
1. [X] Config option, meta tests
2. [X] Fix broken tests
3. [X] Step
4. [X] Builder
5. [X] Validator [7/7]
1. [X] XSD with no grammer should not set warning
2. [X] XSD with grammer should set warning
3. [X] Preprocess with XSD no grammar (1 warning)
4. [X] Preprocess with XSD grammer (2 warnings)
5. [X] Many preprocesses (1 warning)
6. [X] Existing warning preserved
7. [X] Warn Agent config takes effect
** TODO Compile Performance improvements
** TODO Assign CONTENT_FAIL to XSL steps in builder
** TODO Investigate whether there is an issue with rax:roles with other headers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ object Wadl2Checker {
val anyMatch = parser.flag[Boolean] (List("a", "disable-any-match"),
"Disable any match extension : false")

val warnHeaders = parser.flag[Boolean] (List("W", "disable-warn-headers"),
"Disable warn headers : false")

val warnAgent = parser.option[String] (List("A", "warn-agent"), "agent-name",
"The name of the agent used in WARNING headers. Default: -")

val dontValidate = parser.flag[Boolean] (List("D", "dont-validate"),
"Don't validate produced checker Default: false")

Expand Down Expand Up @@ -166,6 +172,8 @@ object Wadl2Checker {
c.preserveRequestBody = preserveRequestBody.value.getOrElse(false)
c.doXSDGrammarTransform = xsdGrammarTransform.value.getOrElse(false)
c.validateChecker = !dontValidate.value.getOrElse(false)
c.enableWarnHeaders = !warnHeaders.value.getOrElse(false)
c.warnAgent = warnAgent.value.getOrElse("-")

new WADLCheckerBuilder().build(getSource, getResult, c)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ object Wadl2Dot {
val anyMatch = parser.flag[Boolean] (List("a", "disable-any-match"),
"Disable any match extension : false")

val warnHeaders = parser.flag[Boolean] (List("W", "disable-warn-headers"),
"Disable warn headers : false")

val warnAgent = parser.option[String] (List("A", "warn-agent"), "agent-name",
"The name of the agent used in WARNING headers. Default: -")


val dontValidate = parser.flag[Boolean] (List("D", "dont-validate"),
"Don't validate produced checker Default: false")
Expand Down Expand Up @@ -169,6 +175,9 @@ object Wadl2Dot {
c.preserveRequestBody = preserveRequestBody.value.getOrElse(false)
c.doXSDGrammarTransform = xsdGrammarTransform.value.getOrElse(false)
c.validateChecker = !dontValidate.value.getOrElse(false)
c.enableWarnHeaders = !warnHeaders.value.getOrElse(false)
c.warnAgent = warnAgent.value.getOrElse("-")


new WADLDotBuilder().build(getSource, getResult,
c,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ object WadlTest {
val anyMatch = parser.flag[Boolean] (List("a", "disable-any-match"),
"Disable any match extension : false")

val warnHeaders = parser.flag[Boolean] (List("W", "disable-warn-headers"),
"Disable warn headers : false")

val warnAgent = parser.option[String] (List("A", "warn-agent"), "agent-name",
"The name of the agent used in WARNING headers. Default: -")


val dontValidate = parser.flag[Boolean] (List("D", "dont-validate"),
"Don't validate produced checker Default: false")

Expand Down Expand Up @@ -269,6 +276,9 @@ object WadlTest {
c.preserveRequestBody = preserveRequestBody.value.getOrElse(false)
c.doXSDGrammarTransform = xsdGrammarTransform.value.getOrElse(false)
c.validateChecker = !dontValidate.value.getOrElse(false)
c.enableWarnHeaders = !warnHeaders.value.getOrElse(false)
c.warnAgent = warnAgent.value.getOrElse("-")


val dot = File.createTempFile("chk", ".dot")
dot.deleteOnExit()
Expand Down
28 changes: 28 additions & 0 deletions core/src/main/resources/xsd/checker.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
<alternative test="@type eq 'XPATH'" type="chk:XPathStep"/>
<alternative test="@type eq 'XSL'" type="chk:XSLStep"/>
<alternative test="@type eq 'SET_HEADER'" type="chk:SetHeaderStep"/>
<alternative test="@type eq 'SET_HEADER_ALWAYS'" type="chk:SetHeaderAlwaysStep"/>
<alternative test="@type eq 'HEADER'" type="chk:HeaderStep"/>
<alternative test="@type eq 'HEADERXSD'" type="chk:HeaderXSDStep"/>
<alternative test="@type eq 'HEADER_SINGLE'" type="chk:HeaderSingleStep"/>
Expand Down Expand Up @@ -376,6 +377,32 @@
</complexContent>
</complexType>

<complexType name="SetHeaderAlwaysStep">
<annotation>
<documentation xmlns:html="http://www.w3.org/1999/xhtml">
<html:p>
Adds a header to the step context at this step,
regardless of whether the header is already set.
</html:p>
<html:p>
This step does not actually cause an assertion to
occur, so the test never fails. If a header with
the name does not exist, a new header will be
added to the context with the given value. If a
header with the name already exist then an
additional header value will be set for that
header.
</html:p>
</documentation>
</annotation>
<complexContent>
<extension base="chk:ConnectedStep">
<attribute name="name" type="xsd:string" use="required"/>
<attribute name="value" type="xsd:string" use="required"/>
</extension>
</complexContent>
</complexType>

<complexType name="HeaderStep">
<annotation>
<documentation xmlns:html="http://www.w3.org/1999/xhtml">
Expand Down Expand Up @@ -778,6 +805,7 @@
<enumeration value="XPATH"/>
<enumeration value="XSL"/>
<enumeration value="SET_HEADER"/>
<enumeration value="SET_HEADER_ALWAYS"/>
<enumeration value="HEADER"/>
<enumeration value="HEADERXSD"/>
<enumeration value="HEADER_ANY"/>
Expand Down
49 changes: 48 additions & 1 deletion core/src/main/resources/xsl/builder.xsl
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@
<config option="checkHeaders" value="true"/>
<config option="enableRaxRolesExtension" value="false"/>
<config option="maskRaxRoles403" value="false"/>
<config option="enableWarnHeaders" value="true"/>
<config option="warnAgent" value="-"/>
</meta>
</params>
</xsl:param>
Expand All @@ -94,6 +96,8 @@
<xsl:variable name="enableRaxRolesExtension" as="xsd:boolean" select="xsd:boolean(check:optionValue($configMetadata, 'enableRaxRolesExtension'))"/>
<xsl:variable name="enableCaptureHeaderExtension" as="xsd:boolean" select="xsd:boolean(check:optionValue($configMetadata, 'enableCaptureHeaderExtension'))"/>
<xsl:variable name="enableAnyMatchExtension" as="xsd:boolean" select="xsd:boolean(check:optionValue($configMetadata, 'enableAnyMatchExtension'))"/>
<xsl:variable name="enableWarnHeaders" as="xsd:boolean" select="xsd:boolean(check:optionValue($configMetadata, 'enableWarnHeaders'))"/>
<xsl:variable name="warnAgent" as="xsd:string" select="check:optionValue($configMetadata,'warnAgent')"/>

<!-- Do we have an XSD? -->
<xsl:variable name="WADLhasXSD" as="xsd:boolean"
Expand Down Expand Up @@ -143,6 +147,8 @@
select="$enableMessageExtension or $useRaxRoles"/>
<xsl:variable name="useCaptureHeaderExtension" as="xsd:boolean"
select="$enableCaptureHeaderExtension"/>
<xsl:variable name="useWarnHeaders" as="xsd:boolean"
select="$enableWarnHeaders and ($useXSDTransform or $usePreProcessExtension)"/>

<!-- Defaults Steps -->
<xsl:variable name="START" select="'S0'"/>
Expand Down Expand Up @@ -893,6 +899,12 @@
<xsl:value-of select="$in = 'application/json' or ends-with($in,'+json')"/>
</xsl:function>

<xsl:function name="check:WarnID" as="xsd:string">
<xsl:param name="context" as="node()"/>
<xsl:param name="number" as="xsd:integer"/>
<xsl:value-of select="concat(generate-id($context),'Wrn',$number)"/>
</xsl:function>

<xsl:function name="check:WellFormID" as="xsd:string">
<xsl:param name="context" as="node()"/>
<xsl:value-of select="concat(generate-id($context),'W')"/>
Expand Down Expand Up @@ -987,6 +999,10 @@
select="check:XPathID(.,0)"/>
<xsl:variable name="FAILID" as="xsd:string"
select="check:WellFormFailID(.)"/>
<xsl:variable name="TRANSFORM_PREID" as="xsd:string"
select="check:WarnID(.,0)"/>
<xsl:variable name="TRANSFORM_XSDID" as="xsd:string"
select="check:WarnID(.,1)"/>
<step type="{$type}" id="{check:WellFormID(.)}">
<xsl:choose>
<xsl:when test="$doElement">
Expand Down Expand Up @@ -1104,6 +1120,9 @@
<xsl:attribute name="next" select="($XSDID, $FAILID)"
separator=" "/>
</xsl:when>
<xsl:when test="$useWarnHeaders">
<xsl:attribute name="next" select="$TRANSFORM_PREID"/>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="next" select="$ACCEPT"/>
</xsl:otherwise>
Expand All @@ -1117,12 +1136,40 @@
<xsl:copy-of select="child::*"/>
</step>
</xsl:for-each>
<xsl:if test="$useWarnHeaders">
<step type="SET_HEADER_ALWAYS" id="{$TRANSFORM_PREID}" name="Warning" value='214 {$warnAgent} "Preprocess Transformation Applied"' next="{$ACCEPT}"/>
</xsl:if>
</xsl:if>
<xsl:if test="$doJSON">
<step type="JSON_SCHEMA" id="{$JSONID}" next="{$ACCEPT}"/>
</xsl:if>
<xsl:if test="$doXSD">
<step type="XSD" id="{$XSDID}" next="{$ACCEPT}"/>
<step type="XSD" id="{$XSDID}">
<xsl:choose>
<xsl:when test="$useWarnHeaders and $useXSDTransform">
<xsl:attribute name="next" select="$TRANSFORM_XSDID"/>
</xsl:when>
<xsl:when test="$useWarnHeaders and $doPreProcess">
<xsl:attribute name="next" select="$TRANSFORM_PREID"/>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="next" select="$ACCEPT"/>
</xsl:otherwise>
</xsl:choose>
</step>
<xsl:if test="$useWarnHeaders and $useXSDTransform">
<step type="SET_HEADER_ALWAYS" id="{$TRANSFORM_XSDID}" name="Warning"
value='214 {$warnAgent} "Default values may have been filled in by XSD processor"'>
<xsl:choose>
<xsl:when test="$doPreProcess">
<xsl:attribute name="next" select="$TRANSFORM_PREID"/>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="next" select="$ACCEPT"/>
</xsl:otherwise>
</xsl:choose>
</step>
</xsl:if>
</xsl:if>
<step type="CONTENT_FAIL" id="{$FAILID}"/>
</xsl:template>
Expand Down
7 changes: 4 additions & 3 deletions core/src/main/resources/xsl/checker2dot.xsl
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@
<xsl:text>ε (</xsl:text>
<xsl:choose>
<xsl:when test="$nextStep/@name">
<xsl:value-of select="concat($nextStep/@name,':&#x2190;? ',$nextStep/@value)"/>
<xsl:value-of select="concat($nextStep/@name,':&#x2190;? ',check:escapeRegex($nextStep/@value))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="check:matchValue($nextStep)"/>
Expand Down Expand Up @@ -212,7 +212,7 @@
<xsl:value-of select="concat(@name,' : ',check:matchValue(.))"/>
</xsl:when>
<xsl:when test="@name and @value">
<xsl:value-of select="concat(@name,':&#x2190;? ',@value)"/>
<xsl:value-of select="concat(@name,':&#x2190;? ',check:escapeRegex(@value))"/>
</xsl:when>
<xsl:when test="@match or @matchRegEx">
<xsl:value-of select="check:matchValue(.)"/>
Expand Down Expand Up @@ -288,7 +288,8 @@
</xsl:template>
<xsl:function name="check:escapeRegex" as="xsd:string">
<xsl:param name="in" as="xsd:string"/>
<xsl:value-of select="replace($in,'\\','\\\\')"/>
<xsl:variable name="pass1" as="xsd:string" select="replace($in,'\\','\\\\')"/>
<xsl:value-of select="replace($pass1,'&quot;','\\&quot;')"/>
</xsl:function>
<xsl:function name="check:notMatchRegex" as="xsd:string">
<xsl:param name="in" as="xsd:string*"/>
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/resources/xsl/opt/removeDups-rules.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@
Rule for HEADER_ALL. Although both match and matchRegEx are
optional at least one of these should be specified.
</rule>
<rule types="SET_HEADER" required="next name value">
Rule for SET_HEADER.
<rule types="SET_HEADER SET_HEADER_ALWAYS" required="next name value">
Rule for SET_HEADER and SET_HEADER_ALWAYS.
</rule>
<rule types="METHOD REQ_TYPE" required="next match">
Steps that simply contain a match.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,24 @@ class Config {
@AffectsChecker
var preserveRequestBody : Boolean = false

//
// The name of the agent used in WARNING headers. Can be a host:port
// or a pseudonym, using - as the name allowed when the name of the
// agent is unknown.
//
@BeanProperty
@AffectsChecker
var warnAgent : String = "-"

//
// Enable warning headers per RFC 7234. These warnings come into
// play when a transformation is applied to a message body, for
// example.
//
@BeanProperty
@AffectsChecker
var enableWarnHeaders : Boolean = true

/**
* Returns checker metadata (<meta/> element in checker format) for
* the config options that affect the checker.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/***
* Copyright 2016 Rackspace US, 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 com.rackspace.com.papi.components.checker.step

import javax.servlet.FilterChain

import com.rackspace.com.papi.components.checker.servlet._
import com.rackspace.com.papi.components.checker.step.base.{ConnectedStep, Step, StepContext}
import com.rackspace.com.papi.components.checker.util.HeaderUtil._


class SetHeaderAlways(id : String, label : String, val name : String, val value : String,
next : Array[Step]) extends ConnectedStep(id, label, next) {

override def checkStep(req : CheckerServletRequest, resp : CheckerServletResponse, chain : FilterChain, context : StepContext) : Option[StepContext] = {
//
// Always set a header with the given value when this step
// executes. If an existing header exists a new value will be
// added.
//
Some(context.copy(requestHeaders = context.requestHeaders.addHeader(name, value)))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ class StepHandler(var contentHandler : ContentHandler, val config : Config) exte
case "HEADERXSD_ANY" => addHeaderXSDAny(atts)
case "HEADER_ALL" => addHeaderAll(atts)
case "SET_HEADER" => addSetHeader(atts)
case "SET_HEADER_ALWAYS" => addSetHeaderAlways(atts)
case "JSON_SCHEMA" => addJSONSchema(atts)
}
case "grammar" =>
Expand Down Expand Up @@ -916,6 +917,17 @@ class StepHandler(var contentHandler : ContentHandler, val config : Config) exte
steps += (id -> new SetHeader(id, label, name, value, new Array[Step](nexts.length)))
}

private[this] def addSetHeaderAlways(atts : Attributes) : Unit = {
val nexts : Array[String] = atts.getValue("next").split(" ")
val id : String = atts.getValue("id")
val label : String = atts.getValue("label")
val name : String = atts.getValue("name")
val value : String = atts.getValue("value")

next += (id -> nexts)
steps += (id -> new SetHeaderAlways(id, label, name, value, new Array[Step](nexts.length)))
}

private[this] def addMethod(atts : Attributes) : Unit = {
val nexts : Array[String] = atts.getValue("next").split(" ")
val id : String = atts.getValue("id")
Expand Down
Loading

0 comments on commit 572ed73

Please sign in to comment.