Skip to content

Commit

Permalink
Profile resolver: Metadata tests and way of determining top UUID (#1175)
Browse files Browse the repository at this point in the history
- Metadata tests and way of determining top UUID
- Remove global parameter assign-uuid (note backward incompatibility - is it OK?)
- Instead, support global parameters uuid-method and top-uuid, in uuid-method-choice.xsl
- Support global parameter hide-source-profile-uri
- Stub of opr:oscal-version function
- Stub of message handler template, using xsl:message for now
- Add XSpec tests
- Add table of parameters and clarify testing folder content
- Change "home" from global param to global variable; does not need to be set from outside
  • Loading branch information
galtm authored and david-waltermire committed Jun 21, 2022
1 parent b46d555 commit 4d2ca31
Show file tree
Hide file tree
Showing 9 changed files with 621 additions and 187 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
xmlns:opr="http://csrc.nist.gov/ns/oscal/profile-resolution"
stylesheet="../../../utils/util/resolver-pipeline/oscal-profile-RESOLVE.xsl"
run-as="external">
<!--<x:param name="assign-uuid">00000000-0000-4000-A000-000000000000</x:param>-->
<!--<x:param name="top-uuid">00000000-0000-4000-A000-000000000000</x:param>
<x:param name="uuid-method" select="'user-provided'"/>-->

<!-- x:description/@run-as='external' permits the context item to be determined dynamically per scenario
cf https://github.com/xspec/xspec/wiki/External-Transformation#global-context-item
Expand Down
19 changes: 19 additions & 0 deletions src/utils/util/resolver-pipeline/message-handler.xsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:mh="http://csrc.nist.gov/ns/message"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">

<xsl:template name="mh:message-handler">
<xsl:param name="text" as="xs:string"/>
<xsl:param name="message-type" as="xs:string?"/><!-- e.g., 'Error', 'Warning' -->
<xsl:param name="error-code" as="xs:string?"/>
<xsl:param name="terminate" as="xs:boolean" select="false()"/>
<xsl:message expand-text="yes" terminate="{$terminate}">{
string-join(($message-type, $error-code, $text),': ')
}</xsl:message>
</xsl:template>

</xsl:stylesheet>
51 changes: 15 additions & 36 deletions src/utils/util/resolver-pipeline/oscal-profile-RESOLVE.xsl
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:opr="http://csrc.nist.gov/ns/oscal/profile-resolution"
xmlns:u="http://csrc.nist.gov/ns/uuid"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
xmlns:javaUUID="java.util.UUID"
xmlns:r="http://csrc.nist.gov/ns/random"
xpath-default-namespace="http://csrc.nist.gov/ns/oscal/1.0">

<!--
Expand All @@ -22,8 +21,10 @@

<xsl:output method="xml" indent="yes"/>

<xsl:import href="random-util.xsl"/>

<!-- uuid-method-choice.xsl has global parameters
$uuid-method, $top-uuid, and $uuid-service. -->
<xsl:import href="uuid-method-choice.xsl"/>

<xsl:strip-space
elements="catalog group control param guideline select part
metadata back-matter annotation party person org rlink address resource role responsible-party citation
Expand All @@ -35,43 +36,19 @@

<xsl:param name="trace" as="xs:string">off</xsl:param>

<!-- Provide a top-level UUID for the result catalog as $assign-uuid,
or assign-uuid=make-uuid for a native (XSLT 3.0) function call. -->
<xsl:param name="assign-uuid" as="xs:string?"/>
<!-- Alternatively, call a web site if the processor supports it -->
<xsl:param name="uuid-service" select="'https://www.uuidgenerator.net/api/version4'"/>

<!-- For checking a UUID before assigning it. -->
<xsl:variable name="uuid-v4-regex" as="xs:string">^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$</xsl:variable>

<xsl:param name="uri-stack" as="xs:anyURI*" select="()"/>

<xsl:variable name="use-uuid" as="xs:string">
<xsl:choose>
<xsl:when test="matches($assign-uuid,$uuid-v4-regex)">
<xsl:sequence select="$assign-uuid"/>
</xsl:when>
<xsl:when use-when="function-available('random-number-generator')" test="$assign-uuid = 'make-uuid'">
<!-- seed splices a timestamp with a given @uuid -->
<xsl:sequence select="r:make-uuid( /*/@uuid || '-' || replace( string(current-dateTime()),'\D','') )"/>
</xsl:when>
<xsl:when use-when="function-available('javaUUID:randomUUID')" test="function-available('javaUUID:randomUUID')">
<xsl:sequence select="javaUUID:randomUUID()"/>
</xsl:when>
<xsl:when test="unparsed-text-available($uuid-service)">
<xsl:sequence select="unparsed-text($uuid-service)"/>
</xsl:when>
<xsl:otherwise>00000000-0000-4000-B000-000000000000</xsl:otherwise>
</xsl:choose>
</xsl:variable>


<!-- $path-to-source should point back to the location of the source catalog (or profile) from its result,
so '..' is appropriate when writing results down a directory. -->
<xsl:param name="path-to-source" as="xs:string?"/>

<xsl:variable name="louder" select="$trace = 'on'"/>

<xsl:param name="home" select="/"/>
<xsl:variable name="home" select="/"/>

<!-- If true, do not record profile URI in output catalog's source-profile
metadata, due to privacy or security concerns. -->
<xsl:param name="hide-source-profile-uri" as="xs:boolean" select="false()"/>

<!-- The $transformation-sequence declares transformations to be applied in order. -->
<xsl:variable name="transformation-sequence">
Expand Down Expand Up @@ -109,8 +86,10 @@
<xsl:template mode="opr:provide-parameters" match="opr:transform"/>

<xsl:template mode="opr:provide-parameters" match="opr:transform[.='oscal-profile-resolve-metadata.xsl']">
<!-- Since the fallback is not a valid URI the processor will try Java -->
<xsl:map-entry key="QName('','assign-uuid')" select="$use-uuid"/>
<xsl:map-entry key="QName('','top-uuid-computed')">
<xsl:call-template name="u:determine-uuid"/>
</xsl:map-entry>
<xsl:map-entry key="QName('','hide-source-profile-uri')" select="$hide-source-profile-uri"/>
</xsl:template>

<!-- for opr:transformation, the semantics are "apply this XSLT" -->
Expand Down
76 changes: 61 additions & 15 deletions src/utils/util/resolver-pipeline/oscal-profile-resolve-metadata.xsl
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,53 @@
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
xmlns:opr="http://csrc.nist.gov/ns/oscal/profile-resolution"
exclude-result-prefixes="xs math o opr"
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:
a) When oscal-profile-RESOLVE.xsl invokes this transform,
it passes in a precomputed value for $top-uuid-computed.
b) Any other caller of this transform should pass in
$uuid-method and, if applicable, $top-uuid and $uuid-service,
but not $top-uuid-computed. As a result, this transform
will compute $top-uuid-computed.
-->

<!-- uuid-method-choice.xsl has global parameters
$uuid-method, $top-uuid, and $uuid-service. -->
<xsl:import href="uuid-method-choice.xsl"/>

<!-- Top-level UUID for result catalog. -->
<xsl:param name="top-uuid-computed" as="xs:string">
<xsl:call-template name="u:determine-uuid"/>
</xsl:param>

<xsl:param name="profile-origin-uri">urn:UNKNOWN</xsl:param>

<!-- Accepts a uuid as $assign-uuid, or provides a dummy UUID. A calling application can provide a 'random' UUID. -->
<xsl:param name="assign-uuid" as="xs:string?"/>

<xsl:variable name="uuid-v4-regex" as="xs:string">^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$</xsl:variable>
<!-- If true, do not record profile URI in output catalog's source-profile
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()"/>

<xsl:variable name="use-uuid" as="xs:string"
select="( $assign-uuid[matches(.,$uuid-v4-regex)], '00000000-0000-4000-B000-000000000000' )[1]"/>

<xsl:template match="* | @*" mode="#all">
<!-- 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"/>

<xsl:template match="/ | * | @*" mode="#all">
<xsl:copy>
<xsl:apply-templates mode="#current" select="node() | @*"/>
</xsl:copy>
</xsl:template>

<xsl:template match="profile" priority="1">
<catalog uuid="{ $use-uuid }">
<catalog uuid="{ $top-uuid-computed }">
<!-- Rewriting top-level @id -->
<!--<xsl:if test="function-available('uuid:randomUUID')" xmlns:uuid="java:java.util.UUID">
<xsl:attribute name="uuid" select="uuid:randomUUID()"/>
Expand All @@ -38,12 +62,13 @@
</xsl:template>

<xsl:template match="profile/metadata">
<xsl:variable name="leaders" select="(title | published | last-modified | version | oscal-version | doc-id)"/>
<xsl:variable name="leaders" select="(title | published | last-modified | version | oscal-version | document-id)"/>
<xsl:copy>
<xsl:apply-templates mode="#current" select="@*"/>
<xsl:apply-templates mode="#current" select="$leaders"/>
<xsl:apply-templates mode="#current" select="prop"/>
<link href="{$profile-origin-uri}" rel="resolution-source"/>
<prop name="resolution-tool" value="OSCAL Profile Resolver XSLT Pipeline OPRXP"/>
<link href="{$source-profile}" rel="source-profile"/>
<xsl:apply-templates mode="#current" select="* except ($leaders | prop)"/>
<!--<xsl:apply-templates select="../selection" mode="imported-metadata"/>-->
</xsl:copy>
Expand All @@ -55,13 +80,34 @@
</xsl:copy>
</xsl:template>

<xsl:template match="profile/metadata/oscal-version">
<xsl:copy>
<xsl:sequence select="opr:oscal-version(
.,
ancestor::profile/selection/metadata/oscal-version/normalize-space(),
$tool-oscal-version
)"/>
</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">
<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'"/>
</xsl:function>

<!--<xsl:template match="selection" mode="imported-metadata">
<resource id="{@id}-RESOURCE" rel="imported">
<xsl:copy-of select="metadata/title" copy-namespaces="no"/>
<rlink href="{@opr:src}"/>
</resource>
</xsl:template>-->

<xsl:template match="selection/metadata"/>

</xsl:stylesheet>
46 changes: 31 additions & 15 deletions src/utils/util/resolver-pipeline/readme.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,49 @@
## Resolver pipeline
## Profile Resolver Pipeline

Profile resolution is implemented here as a set of XSLT transformations to be performed in sequence, applied to defined inputs (a **source profile** with imported **catalog** sources) to produce defined outputs (a **profile resolution result** in the form of a catalog). The word **baseline** is also used to refer to a particular profile in application, whether in its unprocessed form or its resolved, serialized form.
Profile resolution is implemented here as a sequence of XSLT transformations. The sequence applies to defined inputs (a **source profile** with imported **catalog** sources) and produces defined outputs (a **profile resolution result** in the form of a catalog). The word **baseline** also refers to a particular profile in application, whether in its unprocessed form or its resolved, serialized form.

The sequence reflects and roughly corresponds to the three steps in profile resolution described for OSCAL in the [Profile Resolution Specification](https://pages.nist.gov/OSCAL/concepts/processing/profile-resolution/):
The sequence of XSLT transformations reflects and roughly corresponds to the steps in profile resolution described for OSCAL in the [Profile Resolution Specification](https://pages.nist.gov/OSCAL/concepts/processing/profile-resolution/):

- **selection** (importing catalogs or profiles and selecting controls from them)
- **selection**: importing catalogs or profiles, and selecting controls from them

- **organization (merging)** i.e. specifying how selected controls are to be organized in representation
- **organization (merging)**: organizing the selected controls for the output representation

- **modification** - setting parameters and potentially supplementing, amending or editing control text
- **modification**: setting parameters and potentially supplementing, amending or editing control text

For demonstration, the expected interim results for test files are kept in the testing/\* folders
### Tests for this Implementation

Note that these interim results are *not always valid to any OSCAL schema* while at the same time they are quite close to OSCAL profile and catalog syntax.
The `testing/*` folders contain XSpec tests that indicate expected interim results of each XSLT transformation in the sequence.

Testing files for profile resolution in general are kept [with the specification](../../../specifications/profile-resolution). The testing files in this subdirectory are only for this implementation.
Note that these interim results are *not necessarily valid to any OSCAL schema*, although they are quite close to OSCAL profile and catalog syntax.

### Invoking the XSLT:
The files in `testing/*` are only for this implementation. Implementation-independent tests and sample files for profile resolution are [with the specification](../../../specifications/profile-resolution).

Use a recent version of Saxon for best results -- although we would also be *very interested* to hear from users of other XSLT engines conformant to the 3.1 family of XML standards (XSLT/XPath/XDM/XQuery).
### Invoking the XSLT

The main entry point for the transformation pipeline is the dynamic build XSLT called `oscal-profile-RESOLVE.xsl`, which invokes the core transformation steps in sequence, taking the source profile document as primary input. Load Saxon with your document and this stylesheet as follows (for example):
Use a recent version of Saxon for best results &#8212; although we would also be *very interested* to hear from users of other XSLT engines conformant to the 3.1 family of XML standards (XSLT/XPath/XDM/XQuery).

The entry point for the transformation pipeline is `oscal-profile-RESOLVE.xsl`, which invokes the transformation steps in sequence, taking the source profile document as primary input. Load Saxon with your document and this stylesheet as follows (for example):

```bash
> java -cp saxon-he-10.0.jar net.sf.saxon.Transform -t -s:YOUR_PROFILE_DOCUMENT.xml -xsl:path/to/oscal-profile-RESOLVE.xsl -o:YOUR_RESULT_BASELINE.xml
```

Alternatively, set up the bindings in an IDE or programmed environment that has XSLT 3.1 support.
You can optionally set one or more of the parameters listed in the following table, using syntax `name=value` at the end of the command above. The sequence of parameters is not significant.

For example,
```bash
> java -cp saxon-he-10.0.jar net.sf.saxon.Transform -t -s:YOUR_PROFILE_DOCUMENT.xml -xsl:path/to/oscal-profile-RESOLVE.xsl -o:YOUR_RESULT_BASELINE.xml uuid-method=random-xslt hide-source-profile-uri=true
```
| Name | Description | Default |
|---|---|---|
| `hide-source-profile-uri` | If `true`, the output catalog's metadata does not record the source profile's URI. | `false` |
| `path-to-source` | Path from output catalog to location of source profile. | None |
| `top-uuid` | UUID value for top-level element in output catalog, if `uuid-method` is `user-provided`. | None |
| `uuid-method` | Method for computing UUID of top-level element in output catalog. Valid values are: `random-xslt`, in which case the `random-number-generator` XPath function must be available; `random-java`, in which case the `java.util.UUID.randomUUID()` Java method must be available; `user-provided`, in which case you must specify the `top-uuid` parameter; `web-service`, referring to the `uuid-service` parameter value; and `fixed`. | `fixed`|
| `uuid-service` | URI for a web service that produces a UUID, if `uuid-method` is `web-service`.| `https://www.uuidgenerator.net/api/version4`|

Alternatively, set up the bindings in an IDE or programming environment that has XSLT 3.1 support.

Note that URIs (addresses) given in a profile document must link correctly as absolute or relative paths to their imported catalogs, as demonstrated in examples.
Note that URIs (addresses) given in a profile document must link correctly as absolute or relative paths to their imported catalogs, as demonstrated in [examples](../../../specifications/profile-resolution/profile-resolution-examples).

A captured and serialized profile resolution will take the form of an OSCAL catalog, and be valid to the catalog schema for correctly formed inputs.
A serialized output of profile resolution takes the form of an OSCAL catalog. Assuming inputs are correctly formed, the output is valid to the catalog schema.
Loading

0 comments on commit 4d2ca31

Please sign in to comment.