Skip to content

Commit

Permalink
Implement opr:oscal-version function
Browse files Browse the repository at this point in the history
This function meets the "req-meta-oscal-version" requirement
(MUST) from usnistgov#1386.

This function partially meets the "req-meta-oscalversion-error"
requirement (MAY) by checking the numeric part of the version string.
It does not check pre-release identifiers, if present in version
strings.
  • Loading branch information
galtm committed Aug 21, 2022
1 parent c4de2fe commit 871ecb1
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 70 deletions.
63 changes: 48 additions & 15 deletions src/utils/util/resolver-pipeline/oscal-profile-resolve-metadata.xsl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
<xsl:stylesheet version="3.0"
xmlns="http://csrc.nist.gov/ns/oscal/1.0"
xmlns:mh="http://csrc.nist.gov/ns/message"
xmlns:o="http://csrc.nist.gov/ns/oscal/1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
Expand All @@ -9,8 +10,7 @@
xmlns:u="http://csrc.nist.gov/ns/uuid"
exclude-result-prefixes="xs math o opr u"
xpath-default-namespace="http://csrc.nist.gov/ns/oscal/1.0" >

<!-- XSLT 2.0 so as to validate against XSLT 3.0 constructs -->


<!-- How to specify top-level UUID for result catalog:
Expand Down Expand Up @@ -38,9 +38,6 @@
metadata, due to privacy or security concerns. This parameter is
passed from oscal-profile-RESOLVE.xsl and the end user can override it. -->
<xsl:param name="hide-source-profile-uri" as="xs:boolean" select="false()"/>

<!-- Version of this resolution tool -->
<xsl:variable name="tool-oscal-version" as="xs:string" select="'1.1.0'"/>

<xsl:variable name="source-profile" as="xs:string"
select="if ($hide-source-profile-uri) then 'profile' else $profile-origin-uri"/>
Expand Down Expand Up @@ -84,21 +81,57 @@
<xsl:copy>
<xsl:sequence select="opr:oscal-version(
.,
ancestor::profile/selection/metadata/oscal-version/normalize-space(),
$tool-oscal-version
ancestor::profile/selection/metadata/oscal-version/normalize-space()
)"/>
</xsl:copy>
</xsl:template>

<!-- If there is a common major version among all inputs,
return the most recent minor version or the tool version,
whichever is earlier.
If there is no common major version, return fatal error. -->
<xsl:function name="opr:oscal-version" as="xs:string">
<!-- Return the oscal-version of the source profile.
Perform error checking on the numeric parts of the inputs only,
ignoring suffixes like "-draft" or "-rc.1".
(Requirement "req-meta-oscalversion-error" has level="may".)
-->
<xsl:function name="opr:oscal-version" as="xs:string" visibility="public">
<!-- Without visiblity="public" the XSpec test returns
XTDE0041: Cannot invoke function opr:oscal-version#2 externally, because it is not public
-->
<xsl:param name="source" as="xs:string"/>
<xsl:param name="imported" as="xs:string*"/>
<xsl:param name="tool" as="xs:string"/>
<xsl:value-of select="'TODO: Not implemented yet'"/>

<!-- Utility XPath functions to convert x.y.z into a single
number that can be compared using "lt". -->
<xsl:variable name="base" as="xs:integer" select="100"/>
<xsl:variable name="numeric-parts" as="function(*)"
select="function($str as xs:string) as xs:integer* {
($str => tokenize('\.|-'))[. castable as xs:integer]
! xs:integer(.)
}"/>
<xsl:variable name="single-number" as="function(*)"
select="function($n as xs:integer+, $base as xs:integer) as xs:integer {
($n[1],0)[1] * $base * $base +
($n[2],0)[1] * $base +
($n[3],0)[1]
}"/>

<!-- Compute single version number for the source and loop over
the imports. -->
<xsl:variable name="profile-version-number" as="xs:integer"
select="$single-number($numeric-parts($source), $base)"/>
<xsl:for-each select="$imported">
<xsl:variable name="imported-version-number" as="xs:integer"
select="$single-number($numeric-parts(.), $base)"/>
<xsl:if test="$profile-version-number lt $imported-version-number">
<xsl:call-template name="mh:message-handler">
<xsl:with-param name="message-type" select="'Error'"/>
<xsl:with-param name="terminate" select="true()"/>
<xsl:with-param name="text" expand-text="yes">Import uses oscal-version of {.
}, which is newer than profile oscal-version of {
$source}.</xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
<!-- Return the version from the source parameter. -->
<xsl:value-of select="$source"/>
</xsl:function>

<!--<xsl:template match="selection" mode="imported-metadata">
Expand Down
79 changes: 24 additions & 55 deletions src/utils/util/resolver-pipeline/testing/2_metadata/metadata.xspec
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,8 @@
<oscal-version>...</oscal-version>
</x:expect>
</x:scenario>
<x:scenario label="Sanity checks for calling opr:oscal-version with correct inputs: " pending="opr:oscal-version not implemented yet">
<x:scenario label="Most recent version is in selection metadata">
<x:scenario label="Sanity checks for calling opr:oscal-version with correct inputs: ">
<x:scenario label="Most recent version is in selection metadata" catch="yes">
<x:context select="/o:profile/o:metadata/o:oscal-version">
<profile>
<metadata>
Expand All @@ -252,9 +252,8 @@
</selection>
</profile>
</x:context>
<x:expect label="Most recent minor version mentioned in document">
<oscal-version>1.0.4</oscal-version>
</x:expect>
<x:expect label="Error"
test="$x:result instance of map(*) and $x:result('err') instance of map(*)"/>
</x:scenario>
<x:scenario label="Most recent version is in profile metadata">
<x:context select="/o:profile/o:metadata/o:oscal-version">
Expand All @@ -269,81 +268,51 @@
</selection>
</profile>
</x:context>
<x:expect label="Most recent minor version mentioned in document">
<x:expect label="Version from profile">
<oscal-version>1.0.4</oscal-version>
</x:expect>
</x:scenario>
<x:scenario label="Tool version is older than document versions">
<x:context select="/o:profile/o:metadata/o:oscal-version">
<profile>
<metadata>
<oscal-version>1.4.0</oscal-version>
</metadata>
<selection>
<metadata>
<oscal-version>1.2.0</oscal-version>
</metadata>
</selection>
</profile>
</x:context>
<x:expect label="Tool version">
<oscal-version>1.1.0</oscal-version>
</x:expect>
</x:scenario>
</x:scenario>
</x:scenario>

<x:scenario label="Tests for opr:oscal-version function" pending="opr:oscal-version not implemented yet">
<x:scenario label="Tests for opr:oscal-version function">
<x:scenario label="Same major version">
<x:scenario label="Source profile version newer than imported document">
<x:scenario label="Source profile version at least as new as all imported documents">
<x:call function="opr:oscal-version">
<x:param name="source" select="'1.0.1'"/>
<x:param name="imported" select="('1.0.0','1.0.0')"/>
<x:param name="tool" select="'1.0.1'"/>
<x:param name="imported" select="('1.0.0','1.0.1')"/>
</x:call>
<x:expect label="Newest minor version" select="'1.0.1'"/>
<x:expect label="Source version and no error" select="'1.0.1'"/>
</x:scenario>
<x:scenario label="Source profile version older than imported document">
<x:scenario label="Source profile version older than at least one imported document" catch="yes">
<x:call function="opr:oscal-version">
<x:param name="source" select="'1.0.0'"/>
<x:param name="imported" select="('1.0.1','1.0.1')"/>
<x:param name="tool" select="'1.0.1'"/>
<x:param name="imported" select="('1.0.0','1.0.1')"/>
</x:call>
<x:expect label="Newest minor version" select="'1.0.1'"/>
<x:expect label="Error"
test="$x:result instance of map(*) and $x:result('err') instance of map(*)"/>
</x:scenario>
<x:scenario label="Tool version is older">
<x:scenario label="Source profile version and imported document differ by their pre-release identifiers">
<x:call function="opr:oscal-version">
<x:param name="source" select="'1.0.1'"/>
<x:param name="imported" select="('1.0.1','1.0.1')"/>
<x:param name="tool" select="'1.0.0'"/>
<x:param name="source" select="'1.0.1-rc1'"/>
<x:param name="imported" select="('1.0.0','1.0.1-rc2')"/>
</x:call>
<x:expect label="Tool version" select="'1.0.0'"/>
<x:expect label="Source version and no error, because this implementation does not check pre-release identifiers"
select="'1.0.1-rc1'"/>
</x:scenario>
</x:scenario>
<x:scenario label="Different major version">
<x:scenario label="Source profile version newer than imported document">
<x:scenario label="Source profile version at least as new as all imported documents">
<x:call function="opr:oscal-version">
<x:param name="source" select="'2.0.1'"/>
<x:param name="imported" select="('1.0.0','1.0.0')"/>
<x:param name="tool" select="'2.0.1'"/>
</x:call>
<x:expect label="Error"
test="$x:result instance of map(*) and $x:result('err') instance of map(*)"/>
</x:scenario>
<x:scenario label="Source profile version older than imported document">
<x:call function="opr:oscal-version">
<x:param name="source" select="'1.0.0'"/>
<x:param name="imported" select="('2.0.1','2.0.1')"/>
<x:param name="tool" select="'2.0.1'"/>
<x:param name="imported" select="('1.37.40','1.37')"/>
</x:call>
<x:expect label="Error"
test="$x:result instance of map(*) and $x:result('err') instance of map(*)"/>
<x:expect label="Source version and no error" select="'2.0.1'"/>
</x:scenario>
<x:scenario label="Tool version is older">
<x:scenario label="Source profile version older than at least one imported document" catch="yes">
<x:call function="opr:oscal-version">
<x:param name="source" select="'2.0.1'"/>
<x:param name="imported" select="('2.0.1','2.0.1')"/>
<x:param name="tool" select="'1.0.0'"/>
<x:param name="source" select="'1.37.40'"/>
<x:param name="imported" select="('2.0','1.0.1')"/>
</x:call>
<x:expect label="Error"
test="$x:result instance of map(*) and $x:result('err') instance of map(*)"/>
Expand Down

0 comments on commit 871ecb1

Please sign in to comment.