Skip to content

Commit

Permalink
BACKLOG-22157 Update api, add dry run property, add tests (#6)
Browse files Browse the repository at this point in the history
* BACKLOG-22157 Update api, add dry run property, add tests

* BACKLOG-22157 Fix test
  • Loading branch information
AKarmanov authored Jan 16, 2024
1 parent 98d90a1 commit 73a7424
Show file tree
Hide file tree
Showing 11 changed files with 227 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.owasp.html.HtmlChangeListener;
import org.owasp.html.PolicyFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.ValueFormatException;
Expand Down Expand Up @@ -129,6 +131,23 @@ public Value beforeSetValue(JCRNodeWrapper node, String name,
}
}

if (filteringConfig.htmlSanitizerDryRun(resolveSite.getSiteKey())) {
logger.info(String.format("Dry run: Skipping Sanitization of [%s]", node.getProperty("text").getRealProperty().getPath()));
policyFactory.sanitize(content, new HtmlChangeListener<Object>() {
@Override
public void discardedTag(@Nullable Object o, String tag) {
logger.info(String.format("Removed tag: %s", tag));
}

@Override
public void discardedAttributes(@Nullable Object o, String tag, String... strings) {
logger.info(String.format("Removed attributes %s for tag %s", String.join(", ", strings), tag));
}
}, null);

return originalValue;
}

String result = policyFactory.sanitize(content);

if (result != content && !result.equals(content)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ public interface RichTextConfigurationInterface {
public JSONObject getMergedJSONPolicy(String... siteKeys);

public boolean configExists(String siteKey);

public boolean htmlSanitizerDryRun(String siteKey);
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,17 @@ public boolean configExists(String siteKey) {
return false;
}

@Override
public boolean htmlSanitizerDryRun(String siteKey) {
if (configExists(siteKey)) {
JSONObject f = configs.get(siteKeyToPid.get(siteKey)).getJSONObject("htmlFiltering");

return f.has("htmlSanitizerDryRun") && f.getBoolean("htmlSanitizerDryRun");
}

return false;
}

private void mergeJsonObject(JSONObject target, JSONObject source) {
for (String key : source.keySet()) {
if (source.get(key) instanceof JSONObject) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
import java.util.Set;

@GraphQLDescription("Model for richtext configuration")
public class GqlRichTextConfig {
public class GqlRichTextConfig implements RichTextConfigInterface {

private Set<String> protocols = new HashSet<>();
private Set<String> elements = new HashSet<>();
private List<GqlRichTextConfigAttribute> attributes = new ArrayList<>();
private GqlRichTextDisallowedConfig disallow = new GqlRichTextDisallowedConfig();

@GraphQLField
@GraphQLName("protocols")
Expand All @@ -36,4 +37,11 @@ public Set<String> getElements() {
public List<GqlRichTextConfigAttribute> getAttributes() {
return attributes;
}

@GraphQLField
@GraphQLName("disallow")
@GraphQLDescription("Disallowed html elements and attributes")
public GqlRichTextDisallowedConfig getDisallow() {
return disallow;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.jahia.modules.richtext.graphql.models;

import graphql.annotations.annotationTypes.GraphQLDescription;
import graphql.annotations.annotationTypes.GraphQLField;
import graphql.annotations.annotationTypes.GraphQLName;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@GraphQLDescription("Model for disallowed richtext configuration")
public class GqlRichTextDisallowedConfig implements RichTextConfigInterface {

private Set<String> protocols = new HashSet<>();
private Set<String> elements = new HashSet<>();
private List<GqlRichTextConfigAttribute> attributes = new ArrayList<>();

@GraphQLField
@GraphQLName("protocols")
@GraphQLDescription("Protocols")
public Set<String> getProtocols() {
return protocols;
}

@GraphQLField
@GraphQLName("elements")
@GraphQLDescription("HTML elements")
public Set<String> getElements() {
return elements;
}

@GraphQLField
@GraphQLName("attributes")
@GraphQLDescription("HTML attributes")
public List<GqlRichTextConfigAttribute> getAttributes() {
return attributes;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.jahia.modules.richtext.graphql.models;

import java.util.List;
import java.util.Set;

public interface RichTextConfigInterface {

public Set<String> getProtocols();
public Set<String> getElements();
public List<GqlRichTextConfigAttribute> getAttributes();
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public List<String> getSitesWithActiveFiltering() {
}

@GraphQLField
@GraphQLName("richTextConfiguration")
@GraphQLName("richtextConfiguration")
@GraphQLDescription("RichText filtering configuration for a given site")
public GqlRichTextConfig getRichTextConfiguration(@GraphQLNonNull @GraphQLName("siteKey") @GraphQLDescription("Site key for the affected site") String siteKey) {
RichTextConfigurationInterface filteringConfig = BundleUtils.getOsgiService(RichTextConfigurationInterface.class, null);
Expand All @@ -93,24 +93,38 @@ public GqlRichTextConfig getRichTextConfiguration(@GraphQLNonNull @GraphQLName("
getElements(config, gqlRichTextConfig);
getAttributes(config, gqlRichTextConfig);

if (config.has("disallow")) {
RichTextConfigInterface disallow = gqlRichTextConfig.getDisallow();
config = config.getJSONObject("disallow");
getProtocols(config, disallow);
getElements(config, disallow);
getAttributes(config, disallow);
}

return gqlRichTextConfig;
}

private void getProtocols(JSONObject config, GqlRichTextConfig gqlRichTextConfig) {
private void getProtocols(JSONObject config, RichTextConfigInterface gqlRichTextConfig) {
if (config.has("protocols")) {
JSONArray protocols = config.getJSONArray("protocols");
protocols.forEach(p -> gqlRichTextConfig.getProtocols().add((String) p));
}
}

private void getElements(JSONObject config, GqlRichTextConfig gqlRichTextConfig) {
private void getElements(JSONObject config, RichTextConfigInterface gqlRichTextConfig) {
if (config.has("elements")) {
JSONArray elements = config.getJSONArray("elements");
elements.forEach(e -> ((JSONObject)e).getJSONArray("name").forEach(el -> gqlRichTextConfig.getElements().add((String) el)));
elements.forEach(e -> {
if (((JSONObject)e).get("name") instanceof JSONArray) {
((JSONObject)e).getJSONArray("name").forEach(el -> gqlRichTextConfig.getElements().add((String) el));
} else {
gqlRichTextConfig.getElements().add(((JSONObject)e).getString("name"));
}
});
}
}

private void getAttributes(JSONObject config, GqlRichTextConfig gqlRichTextConfig) {
private void getAttributes(JSONObject config, RichTextConfigInterface gqlRichTextConfig) {
if (config.has("attributes")) {
JSONArray attributes = config.getJSONArray("attributes");
List<GqlRichTextConfigAttribute> a = gqlRichTextConfig.getAttributes();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
htmlFiltering:
htmlSanitizerDryRun: false
protocols:
- http
- https
Expand Down
73 changes: 73 additions & 0 deletions tests/cypress/e2e/filteringAPI.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {createSite, deleteSite} from '@jahia/cypress';
import {DocumentNode} from 'graphql';
import {enableHtmlFiltering, installConfig} from '../fixtures/utils';

describe('HTML rich text filtering API', () => {
const siteKey = 'filteringSite';
const text = '<div id="myId" role="myRole" removed-attribute="removed">Testing <h1>Testing</h1><p><strong>Testing</strong></p></div>';
let previewMutation: DocumentNode;
let configQuery: DocumentNode;

before(() => {
createSite(siteKey);
installConfig('configs/org.jahia.modules.richtext.config-filteringSite.yml');
enableHtmlFiltering(siteKey);
previewMutation = require('graphql-tag/loader!../fixtures/filteringAPI/preview.graphql');
configQuery = require('graphql-tag/loader!../fixtures/filteringAPI/config.graphql');
});

after(() => {
deleteSite(siteKey);
});

it('filters via API and reports on removed elements/attributes', () => {
cy.apollo({
mutation: previewMutation,
variables: {
text: text,
siteKey: siteKey
}
}).then(response => {
expect(response.data.richtextConfiguration.htmlFiltering.testFiltering.removedAttributes).length(1);
expect(response.data.richtextConfiguration.htmlFiltering.testFiltering.removedAttributes[0].attributes[0]).to.equal('removed-attribute');
expect(response.data.richtextConfiguration.htmlFiltering.testFiltering.removedAttributes[0].element).to.equal('div');
expect(response.data.richtextConfiguration.htmlFiltering.testFiltering.removedElements).length(1);
expect(response.data.richtextConfiguration.htmlFiltering.testFiltering.removedElements).contain('strong');
expect(response.data.richtextConfiguration.htmlFiltering.testFiltering.html).contain('role="myRole"');
expect(response.data.richtextConfiguration.htmlFiltering.testFiltering.html).contain('id="myId"');
expect(response.data.richtextConfiguration.htmlFiltering.testFiltering.html).contain('<p>Testing</p>');
});
});

it('returns a list of configured elements and attributes', () => {
cy.apollo({
query: configQuery,
variables: {
siteKey: siteKey
}
}).then(response => {
expect(response.data.richtextConfiguration.htmlFiltering.richtextConfiguration.attributes).length(37);
expect(response.data.richtextConfiguration.htmlFiltering.richtextConfiguration.attributes.find(a => a.attribute === 'class')).to.deep.equal({
attribute: 'class',
elements: [],
isGlobal: true,
pattern: '(myclass1|myclass2)',
__typename: 'GqlRichTextConfigAttribute'
});
expect(response.data.richtextConfiguration.htmlFiltering.richtextConfiguration.attributes.find(a => a.attribute === 'autoplay')).to.deep.equal({
attribute: 'autoplay',
elements: [
'audio',
'video'
],
isGlobal: false,
pattern: null,
__typename: 'GqlRichTextConfigAttribute'
});
expect(response.data.richtextConfiguration.htmlFiltering.richtextConfiguration.elements).length(70);
expect(response.data.richtextConfiguration.htmlFiltering.richtextConfiguration.protocols).length(3);
expect(response.data.richtextConfiguration.htmlFiltering.richtextConfiguration.disallow.elements).length(1);
expect(response.data.richtextConfiguration.htmlFiltering.richtextConfiguration.disallow.elements).contain('strong');
});
});
});
26 changes: 26 additions & 0 deletions tests/cypress/fixtures/filteringAPI/config.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
query Config($siteKey: String!) {
richtextConfiguration {
htmlFiltering {
richtextConfiguration(siteKey: $siteKey) {
elements
protocols
attributes {
attribute
elements
isGlobal
pattern
}
disallow {
elements
protocols
attributes {
attribute
elements
isGlobal
pattern
}
}
}
}
}
}
17 changes: 17 additions & 0 deletions tests/cypress/fixtures/filteringAPI/preview.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
mutation PreviewFiltering($text: String!, $siteKey: String!) {
richtextConfiguration {
htmlFiltering {
testFiltering(
siteKey: $siteKey
html: $text
) {
html
removedElements
removedAttributes {
element
attributes
}
}
}
}
}

0 comments on commit 73a7424

Please sign in to comment.