diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..c1c05e267 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,26 @@ +version: 2 +updates: + # Maven dependencies (pom.xml) + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + groups: + # collapse routine minor/patch bumps into a single PR to cut noise; majors stay separate + minor-and-patch: + update-types: + - "minor" + - "patch" + + # Base image in the Dockerfile + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "weekly" + + # GitHub Actions used by the workflows + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..5305a8558 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,63 @@ +# LinkedDataHub — Agent Guide + +This document describes how an autonomous agent (or any HTTP/LLM client) drives a **running LinkedDataHub (LDH) instance's HTTP API**. It is the API-usage counterpart to `CLAUDE.md` (which is for contributing to the codebase). + +LinkedDataHub is a data-driven Knowledge Graph platform. Everything — documents, applications, access control, the UI — is RDF, managed over a small, uniform HTTP API and standard protocols. There is no bespoke REST surface to learn: you work with RDF documents and SPARQL. + +## Data model + +- The content is a **hierarchy of documents** (containers and items). A container holds child documents; items are leaves. +- **Every document URL is a named graph.** Reading a document returns the RDF in that graph; writing changes it. This is the [SPARQL 1.1 Graph Store Protocol](https://www.w3.org/TR/sparql11-http-rdf-update/). +- Identifiers are opaque URLs. Do not parse structure out of them; follow links (hypermedia) instead. + +## Authentication + +- **WebID-TLS** (client certificate) is the primary mechanism for programmatic agents. Every request carries the cert; the certificate's WebID is the agent identity. With `curl`: `-E cert.pem:password` (`-k` in dev with self-signed certs). +- **OAuth2 (Google)** and **OpenID Connect (ORCID)** are available for human logins. +- **Delegation**: an authorized secretary agent can act for a principal via the `On-Behalf-Of: ` request header. +- Authorization is WebID-based ACLs (`acl:Read`/`Append`/`Write`/`Control`), enforced per document. A response's `Link` headers advertise the modes the current agent holds on that resource. + +## Reading data + +`GET` a document URL with content negotiation: + +- `Accept: text/turtle` · `application/rdf+xml` · `application/ld+json` · `application/n-triples` (any RDF serialization Jena supports) → the document's RDF. +- `Accept: text/html` → the application shell (Saxon-JS then renders client-side). Request RDF, not HTML, when you want data. + +## Writing data (the discipline) + +Writes go through the **document URLs**, never through the SPARQL endpoint (which is read-only): + +| Intent | Method | Body | Notes | +|--------|--------|------|-------| +| Create a child in a container | `POST` container URL | RDF (e.g. `Content-Type: text/turtle`) | Server mints the child URL and returns it in `Location` | +| Create or replace a document at a known URL | `PUT` document URL | RDF | Replaces the whole named graph | +| Update a document in place | `PATCH` document URL | `Content-Type: application/sparql-update` | A SPARQL Update (`INSERT`/`DELETE`) applied to that named graph | +| Delete a document | `DELETE` document URL | — | Removes the named graph | + +Relative URIs in a request body resolve against the target URL. See `bin/post.sh`, `bin/put.sh`, `bin/patch.sh`, `bin/delete.sh` for exact, working invocations. + +## Querying (read-only) + +The dataspace exposes a **read-only SPARQL 1.1 Query** endpoint (advertised via the Service Description `sd:endpoint`; conventionally `/sparql`). `GET`/`POST` a `SELECT`/`CONSTRUCT`/`DESCRIBE`/`ASK`; results are content-negotiated. The endpoint does **not** accept SPARQL Update — mutate via `PATCH` on document URLs (above). + +Write portable, standard SPARQL: use explicit `GRAPH` patterns, no engine-specific extensions. + +## Content & document model + +- Documents carry ordered **content blocks**. Only `ldh:Object` (an embedded RDF resource view) and `ldh:XHTML` (rich text) are permitted as block values; anything else must be wrapped in an `ldh:Object`. +- **Views** (`ldh:View`) are SPARQL-driven blocks (`SELECT`/`CONSTRUCT`/`DESCRIBE`) rendered as lists, tables, grids, charts, maps, or a graph. +- Forms and validation are ontology-driven (SPIN constructors + SHACL shapes), so instance data is shaped by the app's ontology rather than hardcoded schemas. + +## Dataspaces + +A single instance hosts multiple **dataspaces**, each a subdomain (origin). Each dataspace pairs an end-user app (``) with an admin app at the **`admin.` prefix** (`admin.`) — never an `/admin` path. Admin apps manage ontologies, ACLs, and app settings. + +## Tooling + +- **CLI**: the `bin/` scripts wrap every operation above (`get.sh`, `post.sh`, `put.sh`, `patch.sh`, `delete.sh`, `create-container.sh`, `create-item.sh`, `add-view.sh`, `add-select.sh`, `add-construct.sh`, `add-result-set-chart.sh`, `add-file.sh`, `webid-keygen.sh`). They are the authoritative reference for request shapes. +- **Programmatic / MCP**: [Web-Algebra](https://github.com/AtomGraph/Web-Algebra) is the recommended path for agent-composed workflows — a JSON DSL and MCP server whose operations (create container/item, add view/chart, generate portal, …) compose multi-step LDH writes atomically under WebID auth. + +## Standards + +WebID-TLS · SPARQL 1.1 Query & Update · Graph Store Protocol · Linked Data Templates · SHACL · SPIN · RDF (Turtle/RDF-XML/JSON-LD/N-Triples). LDH composes existing W3C/IETF standards; it does not define new wire protocols. diff --git a/CHANGELOG.md b/CHANGELOG.md index d82acc00c..eb1c7ded1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## [Unreleased] +### Security +- SSRF: `URLValidator` now blocks wildcard/any-local (0.0.0.0, ::) addresses in addition to link-local and private ranges, and checks every address the host resolves to (narrowing the DNS-rebinding window). Loopback stays reachable for same-origin document/WebID dereferencing; the backend triplestore is site-local (already blocked). `ALLOW_INTERNAL_URLS` remains the development escape hatch (LNK-003/LNK-009) +- XXE: added `SecureXML` hardened parser factories — `XSLTMasterUpdater` parses with DTDs and external entities disabled, and the external responses parsed by `ldh:send-request` use secure processing (entity-expansion capped) with external entities disabled (LNK-005 residual) +- Upgraded `java-jwt` 3.19.4 → 4.5.2 on the OAuth2/OIDC verification path +- Documented the pinned-truststore invariant behind the disabled hostname verification on internal HTTP clients + +### Added +- Unit tests for `AuthorizationFilter`: the HTTP-method → ACL access-mode contract (`GET`/`HEAD`→Read, `POST`→Append, `PUT`/`DELETE`/`PATCH`→Write), mode lookup, and the owner Read/Write/Append grant +- Loopback/wildcard `URLValidator` tests; JWKS-based `JWTVerifier` tests (valid, wrong issuer, wrong audience, expired, missing `kid`, bad signature) +- `AGENTS.md`: an agent-facing guide to driving a running instance's HTTP API — data model, WebID auth, read/write discipline (writes via `POST`/`PUT`/`PATCH` on document URLs; read-only SPARQL), content model, dataspaces, tooling +- Dependabot config (`.github/dependabot.yml`) for Maven, the Docker base image, and GitHub Actions updates; routine Maven minor/patch bumps grouped into one PR + +### Changed +- Cache TTLs configurable: the WebID model cache and the JWKS cache now read their expiration (seconds) from `WEBID_CACHE_EXPIRATION` / `JWKS_CACHE_EXPIRATION` (default 86400 = 1 day), via `CATALINA_OPTS` system properties like the `CLIENT_*` timeouts. Lowering `WEBID_CACHE_EXPIRATION` bounds how long a revoked WebID stays authenticated + ## [5.6.0] - 2026-07-08 ### Added - `OntologyRepository` (renamed from `OntologyModelGetter`): a bounded, evicting ontology cache that serves bundled vocabularies without querying SPARQL; per-app creation is thread-safe and each ontology is materialized once under a lock (`owl:imports` closure flattened manually, then RDFS-inferred and materialized). Seeded ad-hoc in `Namespace` diff --git a/Dockerfile b/Dockerfile index afcdb0e8d..98243907d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -117,6 +117,10 @@ ENV CLIENT_CONNECTION_TIME_TO_LIVE=300000 ENV CLIENT_VALIDATE_AFTER_INACTIVITY=10000 +ENV WEBID_CACHE_EXPIRATION=86400 + +ENV JWKS_CACHE_EXPIRATION=86400 + ENV IMPORT_KEEPALIVE= ENV MAX_IMPORT_THREADS=10 diff --git a/platform/entrypoint.sh b/platform/entrypoint.sh index d5c81dd76..1a8915ad6 100755 --- a/platform/entrypoint.sh +++ b/platform/entrypoint.sh @@ -1092,6 +1092,14 @@ if [ -n "$CLIENT_VALIDATE_AFTER_INACTIVITY" ]; then export CATALINA_OPTS="$CATALINA_OPTS -Dcom.atomgraph.linkeddatahub.validateAfterInactivity=$CLIENT_VALIDATE_AFTER_INACTIVITY" fi +if [ -n "$WEBID_CACHE_EXPIRATION" ]; then + export CATALINA_OPTS="$CATALINA_OPTS -Dcom.atomgraph.linkeddatahub.webIDCacheExpiration=$WEBID_CACHE_EXPIRATION" +fi + +if [ -n "$JWKS_CACHE_EXPIRATION" ]; then + export CATALINA_OPTS="$CATALINA_OPTS -Dcom.atomgraph.linkeddatahub.jwksCacheExpiration=$JWKS_CACHE_EXPIRATION" +fi + if [ -n "$MAX_CONTENT_LENGTH" ]; then MAX_CONTENT_LENGTH_PARAM="--stringparam ldhc:maxContentLength '$MAX_CONTENT_LENGTH' " fi diff --git a/pom.xml b/pom.xml index e28cae297..b673a58ae 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.atomgraph linkeddatahub - 5.6.0 + 5.6.1-SNAPSHOT ${packaging.type} AtomGraph LinkedDataHub @@ -46,7 +46,7 @@ https://github.com/AtomGraph/LinkedDataHub scm:git:git://github.com/AtomGraph/LinkedDataHub.git scm:git:git@github.com:AtomGraph/LinkedDataHub.git - linkeddatahub-5.6.0 + linkeddatahub-5.5.4 @@ -169,19 +169,19 @@ ${project.groupId} client - 5.0.2 + 5.0.3-SNAPSHOT classes ${project.groupId} client - 5.0.2 + 5.0.3-SNAPSHOT war com.auth0 java-jwt - 3.19.4 + 4.5.2 net.jodah diff --git a/src/main/java/com/atomgraph/linkeddatahub/Application.java b/src/main/java/com/atomgraph/linkeddatahub/Application.java index 2d429ce4f..9147b37fc 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/Application.java +++ b/src/main/java/com/atomgraph/linkeddatahub/Application.java @@ -288,9 +288,9 @@ public class Application extends ResourceConfig private final KeyStore keyStore, trustStore; private final URI secretaryWebIDURI; private final List supportedLanguages; - private final ExpiringMap webIDmodelCache = ExpiringMap.builder().expiration(1, TimeUnit.DAYS).build(); // TO-DO: config for the expiration period? + private final ExpiringMap webIDmodelCache = ExpiringMap.builder().expiration(Long.parseLong(System.getProperty("com.atomgraph.linkeddatahub.webIDCacheExpiration", "86400")), TimeUnit.SECONDS).build(); // TTL (seconds) configurable via WEBID_CACHE_EXPIRATION; a lower value bounds how long a revoked WebID stays cached private final ExpiringMap oidcModelCache = ExpiringMap.builder().variableExpiration().build(); - private final ExpiringMap jwksCache = ExpiringMap.builder().expiration(1, TimeUnit.DAYS).build(); // Cache JWKS responses + private final ExpiringMap jwksCache = ExpiringMap.builder().expiration(Long.parseLong(System.getProperty("com.atomgraph.linkeddatahub.jwksCacheExpiration", "86400")), TimeUnit.SECONDS).build(); // Cache JWKS responses; TTL (seconds) configurable via JWKS_CACHE_EXPIRATION private final Map xsltExecutableCache = new ConcurrentHashMap<>(); private final MessageDigest messageDigest; private final boolean enableWebIDSignUp; @@ -1519,6 +1519,7 @@ public static Client getClient(KeyStore keyStore, String keyStorePassword, KeySt ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); Registry socketFactoryRegistry = RegistryBuilder.create(). + // hostname verification is safely disabled because ctx trusts only the pinned truststore; revisit if that truststore ever widens to public CAs register("https", new SSLConnectionSocketFactory(ctx, NoopHostnameVerifier.INSTANCE)). register("http", new PlainConnectionSocketFactory()). build(); @@ -1626,6 +1627,7 @@ public static Client getNoCertClient(KeyStore trustStore, Integer maxConnPerRout ctx.init(null, tmf.getTrustManagers(), null); Registry socketFactoryRegistry = RegistryBuilder.create(). + // hostname verification is safely disabled because ctx trusts only the pinned truststore; revisit if that truststore ever widens to public CAs register("https", new SSLConnectionSocketFactory(ctx, NoopHostnameVerifier.INSTANCE)). register("http", new PlainConnectionSocketFactory()). build(); diff --git a/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/auth/IDTokenFilterBase.java b/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/auth/IDTokenFilterBase.java index d3ed486c8..7d42cb81c 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/auth/IDTokenFilterBase.java +++ b/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/auth/IDTokenFilterBase.java @@ -195,7 +195,7 @@ public SecurityContext authenticate(ContainerRequestContext request) else { if (log.isDebugEnabled()) log.debug("ID token for subject '{}' has expired at {}, refresh token not found", jwt.getSubject(), jwt.getExpiresAt()); - throw new TokenExpiredException("ID token for subject '%s' has expired at %s".formatted(jwt.getSubject(), jwt.getExpiresAt())); + throw new TokenExpiredException("ID token for subject '%s' has expired at %s".formatted(jwt.getSubject(), jwt.getExpiresAt()), jwt.getExpiresAt().toInstant()); } } if (!verify(jwt)) return null; diff --git a/src/main/java/com/atomgraph/linkeddatahub/server/util/SecureXML.java b/src/main/java/com/atomgraph/linkeddatahub/server/util/SecureXML.java new file mode 100644 index 000000000..32c8f7240 --- /dev/null +++ b/src/main/java/com/atomgraph/linkeddatahub/server/util/SecureXML.java @@ -0,0 +1,78 @@ +/** + * Copyright 2026 Martynas Jusevičius + * + * 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.atomgraph.linkeddatahub.server.util; + +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; + +/** + * Factory helpers for XML parsers hardened against XXE and entity-expansion (billion laughs) attacks. + * + * @author Martynas Jusevičius {@literal } + * @see OWASP XXE Prevention + */ +public final class SecureXML +{ + + private SecureXML() + { + } + + /** + * Returns a namespace-aware {@link DocumentBuilderFactory} with DTDs and external entities disabled. + * Suitable for parsing trusted internal XML (e.g. stylesheets) that never carries a DOCTYPE. + * + * @return hardened document builder factory + * @throws ParserConfigurationException if a feature cannot be set + */ + public static DocumentBuilderFactory newDocumentBuilderFactory() throws ParserConfigurationException + { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + factory.setXIncludeAware(false); + factory.setExpandEntityReferences(false); + return factory; + } + + /** + * Returns an {@link XMLReader} hardened for parsing untrusted external content. + * Secure processing caps entity expansion (billion laughs) and external entities are disabled, + * while a benign internal DOCTYPE (e.g. XHTML) is still tolerated. + * + * @return hardened XML reader + * @throws ParserConfigurationException if a feature cannot be set + * @throws SAXException if the reader cannot be created + */ + public static XMLReader newXMLReader() throws ParserConfigurationException, SAXException + { + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setNamespaceAware(true); + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + return factory.newSAXParser().getXMLReader(); + } + +} diff --git a/src/main/java/com/atomgraph/linkeddatahub/server/util/URLValidator.java b/src/main/java/com/atomgraph/linkeddatahub/server/util/URLValidator.java index 23e3c92ec..9a1c6fbd1 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/server/util/URLValidator.java +++ b/src/main/java/com/atomgraph/linkeddatahub/server/util/URLValidator.java @@ -52,12 +52,17 @@ public URLValidator(boolean allowInternal) /** * Validates that the URI does not point to an internal/private network address. * Prevents SSRF attacks by blocking access to: - * - RFC 1918 private addresses (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7) + * - Wildcard/any-local addresses (0.0.0.0, ::) + * - RFC 1918 private addresses (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fec0::/10) * - Link-local addresses (169.254.0.0/16, fe80::/10) * - * Note: Loopback addresses (127.0.0.1, localhost, ::1) are NOT blocked as the application - * may legitimately need to access resources on the same server (e.g., transformation queries, - * WebID documents during development, admin operations). + * All addresses the host resolves to are checked (not just the first), narrowing the DNS-rebinding + * window where a host publishes both a public and an internal address. + * + * Loopback addresses (127.0.0.0/8, ::1) are intentionally NOT blocked: LinkedDataHub dereferences + * its own documents and WebIDs on the same origin (which is loopback in local/test deployments), while + * the backend triplestore is reached over a private/site-local address (blocked above), not loopback. + * The {@code ALLOW_INTERNAL_URLS} escape hatch disables all checks for fully-internal deployments. * * @param uri the URI to validate * @return the validated URI @@ -73,18 +78,18 @@ public URI validate(URI uri) String host = uri.getHost(); if (host == null) throw new IllegalArgumentException("URI host cannot be null"); - // Resolve hostname to IP and check if it's private/internal + // Resolve hostname to all IPs and reject if any is wildcard/private/internal (loopback intentionally allowed) try { - InetAddress address = InetAddress.getByName(host); - - // Note: We don't block loopback addresses (127.0.0.1, localhost) because the application - // legitimately accesses its own endpoints for various operations - - if (address.isLinkLocalAddress()) - throw new InternalURLException(uri, address.getHostAddress()); - if (address.isSiteLocalAddress()) - throw new InternalURLException(uri, address.getHostAddress()); + for (InetAddress address : InetAddress.getAllByName(host)) + { + if (address.isAnyLocalAddress()) + throw new InternalURLException(uri, address.getHostAddress()); + if (address.isLinkLocalAddress()) + throw new InternalURLException(uri, address.getHostAddress()); + if (address.isSiteLocalAddress()) + throw new InternalURLException(uri, address.getHostAddress()); + } } catch (UnknownHostException e) { diff --git a/src/main/java/com/atomgraph/linkeddatahub/server/util/XSLTMasterUpdater.java b/src/main/java/com/atomgraph/linkeddatahub/server/util/XSLTMasterUpdater.java index cf37e56c3..d193455dc 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/server/util/XSLTMasterUpdater.java +++ b/src/main/java/com/atomgraph/linkeddatahub/server/util/XSLTMasterUpdater.java @@ -23,8 +23,8 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; import jakarta.servlet.ServletContext; +import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; @@ -203,15 +203,14 @@ public void removePackageImport(Path masterFile, String packagePath) throws IOEx private Document parseDocument(Path file) throws ParserConfigurationException, SAXException, IOException { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(true); - DocumentBuilder builder = factory.newDocumentBuilder(); + DocumentBuilder builder = SecureXML.newDocumentBuilderFactory().newDocumentBuilder(); return builder.parse(file.toFile()); } private void serializeDocument(Document doc, Path file) throws TransformerException { TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); Transformer transformer = transformerFactory.newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "no"); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); diff --git a/src/main/java/com/atomgraph/linkeddatahub/writer/function/SendHTTPRequest.java b/src/main/java/com/atomgraph/linkeddatahub/writer/function/SendHTTPRequest.java index 3847b8a38..647356ef8 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/writer/function/SendHTTPRequest.java +++ b/src/main/java/com/atomgraph/linkeddatahub/writer/function/SendHTTPRequest.java @@ -16,6 +16,7 @@ */ package com.atomgraph.linkeddatahub.writer.function; +import com.atomgraph.linkeddatahub.server.util.SecureXML; import com.atomgraph.linkeddatahub.vocabulary.LDH; import java.io.IOException; import java.io.InputStream; @@ -25,7 +26,8 @@ import jakarta.ws.rs.core.MultivaluedHashMap; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.Response; -import javax.xml.transform.stream.StreamSource; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.sax.SAXSource; import net.sf.saxon.s9api.ExtensionFunction; import net.sf.saxon.s9api.ItemType; import net.sf.saxon.s9api.ItemTypeFactory; @@ -38,6 +40,8 @@ import net.sf.saxon.s9api.XdmValue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; /** * Executes an HTTP request. @@ -117,7 +121,7 @@ public XdmValue call(XdmValue[] arguments) throws SaxonApiException if (cr.hasEntity()) try (InputStream is = cr.readEntity(InputStream.class)) { - return getProcessor().newDocumentBuilder().build(new StreamSource(is)); + return getProcessor().newDocumentBuilder().build(new SAXSource(SecureXML.newXMLReader(), new InputSource(is))); } } else @@ -135,14 +139,14 @@ public XdmValue call(XdmValue[] arguments) throws SaxonApiException if (cr.hasEntity()) try (InputStream is = cr.readEntity(InputStream.class)) { - return getProcessor().newDocumentBuilder().build(new StreamSource(is)); + return getProcessor().newDocumentBuilder().build(new SAXSource(SecureXML.newXMLReader(), new InputSource(is))); } } } - + return XdmEmptySequence.getInstance(); } - catch (IOException ex) + catch (IOException | ParserConfigurationException | SAXException ex) { throw new SaxonApiException(ex); } diff --git a/src/main/webapp/static/com/atomgraph/linkeddatahub/css/bootstrap.css b/src/main/webapp/static/com/atomgraph/linkeddatahub/css/bootstrap.css index 67575805b..0e19e5ebd 100644 --- a/src/main/webapp/static/com/atomgraph/linkeddatahub/css/bootstrap.css +++ b/src/main/webapp/static/com/atomgraph/linkeddatahub/css/bootstrap.css @@ -96,7 +96,7 @@ li button.btn-edit-constructors, li button.btn-add-data, li button.btn-add-ontol .dropdown-menu > li > a.btn-list { background-image: url('../icons/view_list_black_24dp.svg'); background-position: 12px center; background-repeat: no-repeat; padding: 5px 5px 5px 40px; } .dropdown-menu > li > a.btn-table { background-image: url('../icons/ic_border_all_black_24px.svg'); background-position: 12px center; background-repeat: no-repeat; padding: 5px 5px 5px 40px; } .dropdown-menu > li > a.btn-grid { background-image: url('../icons/ic_grid_on_black_24px.svg'); background-position: 12px center; background-repeat: no-repeat; padding: 5px 5px 5px 40px; } -.left-sidebar { display: none; width: 15%; position: fixed; left: 0; top: var(--action-bar-top, 51px); height: calc(100% - var(--action-bar-top, 51px)); z-index: 1001; } +.left-sidebar { display: none; width: 15%; position: fixed; left: 0; top: var(--action-bar-top, 51px); height: calc(100% - var(--action-bar-top, 51px)); overflow-y: auto; z-index: 1001; } @media (max-width: 979px) { body { padding-top: 0; } diff --git a/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/admin/signup.xsl b/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/admin/signup.xsl index da853846e..00e493f4d 100644 --- a/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/admin/signup.xsl +++ b/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/admin/signup.xsl @@ -173,7 +173,7 @@ exclude-result-prefixes="#all"> - + diff --git a/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/block/chart.xsl b/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/block/chart.xsl index 51a53b639..726a0b9fa 100644 --- a/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/block/chart.xsl +++ b/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/block/chart.xsl @@ -183,8 +183,7 @@ exclude-result-prefixes="#all" - - + @@ -2242,7 +2243,7 @@ exclude-result-prefixes="#all" - + diff --git a/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/functions.xsl b/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/functions.xsl index 68d1526fd..47225b0fb 100644 --- a/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/functions.xsl +++ b/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/functions.xsl @@ -45,6 +45,11 @@ exclude-result-prefixes="#all" + + + + + diff --git a/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/navigation.xsl b/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/navigation.xsl index ba5c52a93..f401f975e 100644 --- a/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/navigation.xsl +++ b/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/navigation.xsl @@ -679,7 +679,7 @@ ORDER BY DESC(?created)