diff --git a/CHANGELOG.md b/CHANGELOG.md index dccd99c5a..ef587bffc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ # Changelog All notable changes to this project will be documented in this file. + +## [v1.1.0](https://github.com/solid/community-server/compare/v1.0.0...v1.1.0) - 2021-09-03 + +## Added +* [feat: Throw error when trying to complete interaction out of session](https://github.com/solid/community-server/commit/cb227d6431e8fe891752c7a52d216a0877f9d38e) +* [feat: Indicate to templates if this is part of an auth request](https://github.com/solid/community-server/commit/f71f8683fc0f4e40de2e1c64547397f32c0b6472) +* [feat: Allow filtering in ConstantConverter based on type](https://github.com/solid/community-server/commit/ab06dd30f3f8b0538b693fe50dd3d1f70c035b25) + +## Fixed +* [fix: Allow clients to be remembered in the SessionHttpHandler](https://github.com/solid/community-server/commit/47b3a2d77f4a5b3fa5bab364ac19dc32d79a89c1) +* [fix: Convert data to SparqlDataAccessor in regex config](https://github.com/solid/community-server/commit/f34e124e1b88c59b4e456b3f69d9373e61550bd1) +* [fix(deps): update dependency @solid/access-token-verifier to ^0.12.0](https://github.com/solid/community-server/commit/7928f43f443f914c7850a968912f19a78212d266) + + ## [v1.0.0](https://github.com/solid/community-server/compare/v1.0.0-beta.2...v1.0.0) - 2021-08-04 diff --git a/README.md b/README.md index 40972ced7..aeee635ec 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,8 @@ npm start -- # add parameters if needed ### 📦 Running via Docker Docker allows you to run the server without having Node.js installed: ```shell +git clone https://github.com/solid/community-server.git +cd community-server # Build the Docker image docker build --rm -f Dockerfile -t css:latest . # Run the image, serving your `~/Solid` directory on `http://localhost:3000` diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md new file mode 100644 index 000000000..4c9c2d1be --- /dev/null +++ b/RELEASE_NOTES.md @@ -0,0 +1,8 @@ +# Community Solid Server release notes + +## v1.1.0 +New features: +- The `ConstantConverter` can now filter on media type using the `enabledMediaRanges` and `disabledMediaRanges` options. That way, the server can be configured to bypass a default UI when accessing images or PDF documents. (https://github.com/solid/community-server/discussions/895, https://github.com/solid/community-server/pull/925) + +## v1.0.0 +First release of the Community Solid Server. diff --git a/config/storage/backend/regex.json b/config/storage/backend/regex.json index 09dc72e24..c4578c5fd 100644 --- a/config/storage/backend/regex.json +++ b/config/storage/backend/regex.json @@ -56,11 +56,17 @@ "accessor": { "@id": "urn:solid-server:default:MemoryDataAccessor" } }, { + "comment": "SparqlDataAccessor only accepts quad objects so data to that route needs to be converted", "@id": "urn:solid-server:default:SparqlResourceStore", - "@type": "DataAccessorBasedStore", - "identifierStrategy": { "@id": "urn:solid-server:default:IdentifierStrategy" }, - "auxiliaryStrategy": { "@id": "urn:solid-server:default:AuxiliaryStrategy" }, - "accessor": { "@id": "urn:solid-server:default:SparqlDataAccessor" } + "@type": "RepresentationConvertingStore", + "options_inConverter": { "@id": "urn:solid-server:default:RepresentationConverter" }, + "options_inType": "internal/quads", + "source": { + "@type": "DataAccessorBasedStore", + "identifierStrategy": { "@id": "urn:solid-server:default:IdentifierStrategy" }, + "auxiliaryStrategy": { "@id": "urn:solid-server:default:AuxiliaryStrategy" }, + "accessor": { "@id": "urn:solid-server:default:SparqlDataAccessor" } + } } ] } diff --git a/package-lock.json b/package-lock.json index 0cc02f911..d4eb73a19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,24 +1,24 @@ { "name": "@solid/community-server", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@solid/community-server", - "version": "1.0.0", + "version": "1.1.0", "license": "MIT", "dependencies": { "@comunica/actor-init-sparql": "^1.21.3", "@rdfjs/data-model": "^1.2.0", - "@solid/access-token-verifier": "^0.11.0", + "@solid/access-token-verifier": "^0.12.0", "@types/arrayify-stream": "^1.0.0", "@types/async-lock": "^1.1.2", "@types/bcrypt": "^5.0.0", "@types/cors": "^2.8.10", "@types/end-of-stream": "^1.4.0", "@types/lodash.orderby": "^4.6.6", - "@types/marked": "^2.0.3", + "@types/marked": "^3.0.0", "@types/mime-types": "^2.1.0", "@types/n3": "^1.10.0", "@types/node": "^15.12.5", @@ -45,7 +45,7 @@ "handlebars": "^4.7.7", "jose": "^3.11.6", "lodash.orderby": "^4.6.0", - "marked": "^2.1.3", + "marked": "^3.0.0", "mime-types": "^2.1.32", "n3": "^1.10.0", "nodemailer": "^6.6.2", @@ -4562,9 +4562,9 @@ } }, "node_modules/@solid/access-token-verifier": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@solid/access-token-verifier/-/access-token-verifier-0.11.0.tgz", - "integrity": "sha512-L5XG5qk77FfgfM9uGL16qe08MWt0u5IHz7NsmXEZy8P260RRyt99a2b+nJyb1siXsNC1Z25CtOn1OrPa4Mqjng==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@solid/access-token-verifier/-/access-token-verifier-0.12.0.tgz", + "integrity": "sha512-Lx6bZv8JNSGDklx1vE2wFd2YzCM8GwZQaUSEFHb7Bl3zJqfaC8GabwUub+AmzZsGhwQZmcww3ipaIBhE1Oh0oA==", "dependencies": { "cross-fetch": "^3.1.4", "jose": "^3.14.3", @@ -4927,9 +4927,9 @@ "integrity": "sha512-RaE0B+14ToE4l6UqdarKPnXwVDuigfFv+5j9Dze/Nqr23yyuqdNvzcZi3xB+3Agvi5R4EOgAksfv3lXX4vBt9w==" }, "node_modules/@types/marked": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/marked/-/marked-2.0.3.tgz", - "integrity": "sha512-lbhSN1rht/tQ+dSWxawCzGgTfxe9DB31iLgiT1ZVT5lshpam/nyOA1m3tKHRoNPctB2ukSL22JZI5Fr+WI/zYg==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-3.0.0.tgz", + "integrity": "sha512-vof90OIWT+Tzq3MBRXgV9fsH8PC3WZ4OQg9Qa04vOtP0TcyiNfl7BTonYCmTapHZ5lRZh6ihUYkAy7St1hmk/A==" }, "node_modules/@types/mime": { "version": "1.3.2", @@ -11805,14 +11805,14 @@ } }, "node_modules/marked": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz", - "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-3.0.1.tgz", + "integrity": "sha512-GQtec2MkSCp+ZecV2jBe7+J2fiDXeBFKUBr4981srZDjCaXg0WrtTo9eY2CVc2ZC5YQXkkc0Q8sTZ6c1DB1o2Q==", "bin": { "marked": "bin/marked" }, "engines": { - "node": ">= 10" + "node": ">= 12" } }, "node_modules/media-typer": { @@ -15391,6 +15391,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/typedoc/node_modules/marked": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz", + "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==", + "dev": true, + "bin": { + "marked": "bin/marked" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/typescript": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.4.tgz", @@ -19580,9 +19592,9 @@ } }, "@solid/access-token-verifier": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@solid/access-token-verifier/-/access-token-verifier-0.11.0.tgz", - "integrity": "sha512-L5XG5qk77FfgfM9uGL16qe08MWt0u5IHz7NsmXEZy8P260RRyt99a2b+nJyb1siXsNC1Z25CtOn1OrPa4Mqjng==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@solid/access-token-verifier/-/access-token-verifier-0.12.0.tgz", + "integrity": "sha512-Lx6bZv8JNSGDklx1vE2wFd2YzCM8GwZQaUSEFHb7Bl3zJqfaC8GabwUub+AmzZsGhwQZmcww3ipaIBhE1Oh0oA==", "requires": { "cross-fetch": "^3.1.4", "jose": "^3.14.3", @@ -19939,9 +19951,9 @@ "integrity": "sha512-RaE0B+14ToE4l6UqdarKPnXwVDuigfFv+5j9Dze/Nqr23yyuqdNvzcZi3xB+3Agvi5R4EOgAksfv3lXX4vBt9w==" }, "@types/marked": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/marked/-/marked-2.0.3.tgz", - "integrity": "sha512-lbhSN1rht/tQ+dSWxawCzGgTfxe9DB31iLgiT1ZVT5lshpam/nyOA1m3tKHRoNPctB2ukSL22JZI5Fr+WI/zYg==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-3.0.0.tgz", + "integrity": "sha512-vof90OIWT+Tzq3MBRXgV9fsH8PC3WZ4OQg9Qa04vOtP0TcyiNfl7BTonYCmTapHZ5lRZh6ihUYkAy7St1hmk/A==" }, "@types/mime": { "version": "1.3.2", @@ -25212,9 +25224,9 @@ "dev": true }, "marked": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz", - "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-3.0.1.tgz", + "integrity": "sha512-GQtec2MkSCp+ZecV2jBe7+J2fiDXeBFKUBr4981srZDjCaXg0WrtTo9eY2CVc2ZC5YQXkkc0Q8sTZ6c1DB1o2Q==" }, "media-typer": { "version": "0.3.0", @@ -28001,6 +28013,12 @@ "once": "^1.3.0", "path-is-absolute": "^1.0.0" } + }, + "marked": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz", + "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==", + "dev": true } } }, diff --git a/package.json b/package.json index 31ff5eb41..dc3ed6d5c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@solid/community-server", - "version": "1.0.0", + "version": "1.1.0", "description": "Community Solid Server: an open and modular implementation of the Solid specifications", "keywords": [ "solid", @@ -77,14 +77,14 @@ "dependencies": { "@comunica/actor-init-sparql": "^1.21.3", "@rdfjs/data-model": "^1.2.0", - "@solid/access-token-verifier": "^0.11.0", + "@solid/access-token-verifier": "^0.12.0", "@types/arrayify-stream": "^1.0.0", "@types/async-lock": "^1.1.2", "@types/bcrypt": "^5.0.0", "@types/cors": "^2.8.10", "@types/end-of-stream": "^1.4.0", "@types/lodash.orderby": "^4.6.6", - "@types/marked": "^2.0.3", + "@types/marked": "^3.0.0", "@types/mime-types": "^2.1.0", "@types/n3": "^1.10.0", "@types/node": "^15.12.5", @@ -111,7 +111,7 @@ "handlebars": "^4.7.7", "jose": "^3.11.6", "lodash.orderby": "^4.6.0", - "marked": "^2.1.3", + "marked": "^3.0.0", "mime-types": "^2.1.32", "n3": "^1.10.0", "nodemailer": "^6.6.2", diff --git a/src/storage/conversion/ConstantConverter.ts b/src/storage/conversion/ConstantConverter.ts index 040d5279a..37c8042ef 100644 --- a/src/storage/conversion/ConstantConverter.ts +++ b/src/storage/conversion/ConstantConverter.ts @@ -23,6 +23,14 @@ export interface ConstantConverterOptions { * The minimum requested quality/preference before this should trigger. */ minQuality?: number; + /** + * Media ranges for which the conversion should happen. + */ + enabledMediaRanges?: string[]; + /** + * Media ranges for which the conversion should not happen. + */ + disabledMediaRanges?: string[]; } /** @@ -57,6 +65,8 @@ export class ConstantConverter extends RepresentationConverter { container: options.container ?? true, document: options.document ?? true, minQuality: options.minQuality ?? 0, + enabledMediaRanges: options.enabledMediaRanges ?? [ '*/*' ], + disabledMediaRanges: options.disabledMediaRanges ?? [], }; } @@ -83,10 +93,19 @@ export class ConstantConverter extends RepresentationConverter { throw new NotImplementedHttpError(`Preference is lower than the specified minimum quality`); } + const sourceContentType = representation.metadata.contentType ?? ''; // Do not replace the representation if it already has our content type - if (matchesMediaType(representation.metadata.contentType ?? '', this.contentType)) { + if (matchesMediaType(sourceContentType, this.contentType)) { throw new NotImplementedHttpError(`Representation is already ${this.contentType}`); } + + // Only replace the representation if it matches the media range settings + if (!this.options.enabledMediaRanges.some((type): boolean => matchesMediaType(sourceContentType, type))) { + throw new NotImplementedHttpError(`${sourceContentType} is not one of the enabled media types.`); + } + if (this.options.disabledMediaRanges.some((type): boolean => matchesMediaType(sourceContentType, type))) { + throw new NotImplementedHttpError(`${sourceContentType} is one of the disabled media types.`); + } } public async handle({ representation }: RepresentationConverterArgs): Promise { diff --git a/test/unit/storage/conversion/ConstantConverter.test.ts b/test/unit/storage/conversion/ConstantConverter.test.ts index 27e81ff78..964dc359d 100644 --- a/test/unit/storage/conversion/ConstantConverter.test.ts +++ b/test/unit/storage/conversion/ConstantConverter.test.ts @@ -3,6 +3,7 @@ import arrayifyStream from 'arrayify-stream'; import { RepresentationMetadata } from '../../../../src/ldp/representation/RepresentationMetadata'; import type { ConstantConverterOptions } from '../../../../src/storage/conversion/ConstantConverter'; import { ConstantConverter } from '../../../../src/storage/conversion/ConstantConverter'; +import { CONTENT_TYPE } from '../../../../src/util/Vocabularies'; const createReadStream = jest.spyOn(fs, 'createReadStream').mockReturnValue('file contents' as any); @@ -12,7 +13,7 @@ describe('A ConstantConverter', (): void => { let converter: ConstantConverter; beforeEach(async(): Promise => { - options = { container: true, document: true, minQuality: 1 }; + options = { container: true, document: true, minQuality: 1, enabledMediaRanges: [ '*/*' ], disabledMediaRanges: []}; converter = new ConstantConverter('abc/def/index.html', 'text/html', options); }); @@ -69,6 +70,26 @@ describe('A ConstantConverter', (): void => { await expect(converter.canHandle(args)).rejects.toThrow('Representation is already text/html'); }); + it('does not support representations if their content-type is not enabled.', async(): Promise => { + const preferences = { type: { 'text/html': 1 }}; + const representation = { metadata: new RepresentationMetadata({ [CONTENT_TYPE]: 'text/plain' }) } as any; + const args = { identifier: { path: 'container/' }, representation, preferences }; + + converter = new ConstantConverter('abc/def/index.html', 'text/html', { enabledMediaRanges: [ 'text/turtle' ]}); + + await expect(converter.canHandle(args)).rejects.toThrow('text/plain is not one of the enabled media types.'); + }); + + it('does not support representations if their content-type is disabled.', async(): Promise => { + const preferences = { type: { 'text/html': 1 }}; + const representation = { metadata: new RepresentationMetadata({ [CONTENT_TYPE]: 'text/plain' }) } as any; + const args = { identifier: { path: 'container/' }, representation, preferences }; + + converter = new ConstantConverter('abc/def/index.html', 'text/html', { disabledMediaRanges: [ 'text/*' ]}); + + await expect(converter.canHandle(args)).rejects.toThrow('text/plain is one of the disabled media types.'); + }); + it('supports representations with an unknown content type.', async(): Promise => { const preferences = { type: { 'text/html': 1 }}; const metadata = new RepresentationMetadata();