mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Support acl authorization for IDP components
Configuration has been updated so the IDP requests also pass through an Authorization component. A new config option was added to choose which authorization scheme to use for the IDP.
This commit is contained in:
parent
9968f2ae5b
commit
13c49045d4
@ -8,6 +8,7 @@
|
||||
"files-scs:config/http/middleware/websockets.json",
|
||||
"files-scs:config/http/server-factory/websockets.json",
|
||||
"files-scs:config/http/static/default.json",
|
||||
"files-scs:config/identity/access/public.json",
|
||||
"files-scs:config/identity/email/default.json",
|
||||
"files-scs:config/identity/handler/default.json",
|
||||
"files-scs:config/identity/ownership/token.json",
|
||||
|
@ -8,6 +8,7 @@
|
||||
"files-scs:config/http/middleware/websockets.json",
|
||||
"files-scs:config/http/server-factory/websockets.json",
|
||||
"files-scs:config/http/static/default.json",
|
||||
"files-scs:config/identity/access/public.json",
|
||||
"files-scs:config/identity/email/default.json",
|
||||
"files-scs:config/identity/handler/default.json",
|
||||
"files-scs:config/identity/ownership/token.json",
|
||||
|
@ -8,6 +8,7 @@
|
||||
"files-scs:config/http/middleware/websockets.json",
|
||||
|
||||
"files-scs:config/http/static/default.json",
|
||||
"files-scs:config/identity/access/public.json",
|
||||
"files-scs:config/identity/email/default.json",
|
||||
"files-scs:config/identity/handler/default.json",
|
||||
"files-scs:config/identity/ownership/token.json",
|
||||
|
@ -8,6 +8,7 @@
|
||||
"files-scs:config/http/middleware/websockets.json",
|
||||
"files-scs:config/http/server-factory/websockets.json",
|
||||
"files-scs:config/http/static/default.json",
|
||||
"files-scs:config/identity/access/public.json",
|
||||
"files-scs:config/identity/email/default.json",
|
||||
"files-scs:config/identity/handler/default.json",
|
||||
"files-scs:config/identity/ownership/token.json",
|
||||
|
@ -8,6 +8,7 @@
|
||||
"files-scs:config/http/middleware/websockets.json",
|
||||
"files-scs:config/http/server-factory/websockets.json",
|
||||
"files-scs:config/http/static/default.json",
|
||||
"files-scs:config/identity/access/public.json",
|
||||
"files-scs:config/identity/email/default.json",
|
||||
"files-scs:config/identity/handler/default.json",
|
||||
"files-scs:config/identity/ownership/token.json",
|
||||
|
@ -15,6 +15,7 @@
|
||||
"handlers": [
|
||||
{ "@id": "urn:solid-server:default:StaticAssetHandler" },
|
||||
{ "@id": "urn:solid-server:default:SetupHandler" },
|
||||
{ "@id": "urn:solid-server:default:AuthResourceHttpHandler" },
|
||||
{ "@id": "urn:solid-server:default:IdentityProviderHandler" },
|
||||
{ "@id": "urn:solid-server:default:LdpHandler" }
|
||||
]
|
||||
|
@ -1,6 +1,16 @@
|
||||
# Identity
|
||||
Options related to the Identity Provider.
|
||||
|
||||
## Access
|
||||
Determines how publicly accessible some IDP features are.
|
||||
* *public*: Everything is publicly accessible.
|
||||
* *restricted*: The IDP components use the same authorization scheme as the main LDP component.
|
||||
For example, if the server uses WebACL authorization and the registration endpoint is `/idp/register/`,
|
||||
access to registration can be restricted by creating a valid `/idp/register/.acl` resource.
|
||||
WARNING: This setting will write the necessary resources to the `.well-known` and IDP containers
|
||||
to make this work. Again in the case of WebACL, this means ACL resources allowing full control access.
|
||||
So make sure to update those two containers so only the correct credentials have the correct rights.
|
||||
|
||||
## Email
|
||||
Necessary for sending e-mail when using IDP.
|
||||
* *default*: Disables e-mail functionality.
|
||||
|
27
config/identity/access/initializers/idp.json
Normal file
27
config/identity/access/initializers/idp.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld",
|
||||
"@graph": [
|
||||
{
|
||||
"comment": "Makes sure the IDP container has the necessary root resources.",
|
||||
"@id": "urn:solid-server:default:IdpContainerInitializer",
|
||||
"@type": "ConditionalHandler",
|
||||
"storageKey": "idpContainerInitialized",
|
||||
"storageValue": true,
|
||||
"storage": { "@id": "urn:solid-server:default:SetupStorage" },
|
||||
"source": {
|
||||
"@type": "ContainerInitializer",
|
||||
"args_baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||
"args_path": "/idp/",
|
||||
"args_store": { "@id": "urn:solid-server:default:ResourceStore" },
|
||||
"args_generator": {
|
||||
"@type": "TemplatedResourcesGenerator",
|
||||
"templateFolder": "@css:templates/root/empty",
|
||||
"factory": { "@type": "ExtensionBasedMapperFactory" },
|
||||
"templateEngine": { "@type": "HandlebarsTemplateEngine" }
|
||||
},
|
||||
"args_storageKey": "idpContainerInitialized",
|
||||
"args_storage": { "@id": "urn:solid-server:default:SetupStorage" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
27
config/identity/access/initializers/well-known.json
Normal file
27
config/identity/access/initializers/well-known.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld",
|
||||
"@graph": [
|
||||
{
|
||||
"comment": "Makes sure the .well-known container has the necessary root resources. Some IDP resources are stored there due to OIDC requirements.",
|
||||
"@id": "urn:solid-server:default:WellKnownContainerInitializer",
|
||||
"@type": "ConditionalHandler",
|
||||
"storageKey": "wellKnownContainerInitialized",
|
||||
"storageValue": true,
|
||||
"storage": { "@id": "urn:solid-server:default:SetupStorage" },
|
||||
"source": {
|
||||
"@type": "ContainerInitializer",
|
||||
"args_baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||
"args_path": "/.well-known/",
|
||||
"args_store": { "@id": "urn:solid-server:default:ResourceStore" },
|
||||
"args_generator": {
|
||||
"@type": "TemplatedResourcesGenerator",
|
||||
"templateFolder": "@css:templates/root/empty",
|
||||
"factory": { "@type": "ExtensionBasedMapperFactory" },
|
||||
"templateEngine": { "@type": "HandlebarsTemplateEngine" }
|
||||
},
|
||||
"args_storageKey": "wellKnownContainerInitialized",
|
||||
"args_storage": { "@id": "urn:solid-server:default:SetupStorage" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
13
config/identity/access/public.json
Normal file
13
config/identity/access/public.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld",
|
||||
"@graph": [
|
||||
{
|
||||
"comment": "Allow everyone to register new pods.",
|
||||
"@id": "urn:solid-server:default:IdentityProviderAuthorizingHandler",
|
||||
"AuthorizingHttpHandler:_args_permissionReader": {
|
||||
"@type": "AllStaticReader",
|
||||
"allow": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
22
config/identity/access/restricted.json
Normal file
22
config/identity/access/restricted.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld",
|
||||
"import": [
|
||||
"files-scs:config/identity/access/initializers/idp.json",
|
||||
"files-scs:config/identity/access/initializers/well-known.json"
|
||||
],
|
||||
"@graph": [
|
||||
{
|
||||
"comment": "Use the same authorization for IDP components as is used for LDP, such that for instance registration can be restricted to certain agents.",
|
||||
"@id": "urn:solid-server:default:IdentityProviderAuthorizingHandler",
|
||||
"AuthorizingHttpHandler:_args_permissionReader": { "@id": "urn:solid-server:default:PermissionReader" }
|
||||
},
|
||||
{
|
||||
"comment": "IDP-related containers require initialized resources to support authorization.",
|
||||
"@id": "urn:solid-server:default:ParallelInitializer",
|
||||
"ParallelHandler:_handlers": [
|
||||
{ "@id": "urn:solid-server:default:IdpContainerInitializer" },
|
||||
{ "@id": "urn:solid-server:default:WellKnownContainerInitializer" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -18,6 +18,7 @@
|
||||
"args_handler": { "@id": "urn:solid-server:default:IdentityProviderParsingHandler" }
|
||||
},
|
||||
{
|
||||
"comment": "Handles IDP input parsing.",
|
||||
"@id": "urn:solid-server:default:IdentityProviderParsingHandler",
|
||||
"@type": "ParsingHttpHandler",
|
||||
"args_requestParser": { "@id": "urn:solid-server:default:RequestParser" },
|
||||
@ -25,19 +26,29 @@
|
||||
"args_errorHandler": { "@id": "urn:solid-server:default:ErrorHandler" },
|
||||
"args_responseWriter": { "@id": "urn:solid-server:default:ResponseWriter" },
|
||||
"args_operationHandler": {
|
||||
"@id": "urn:solid-server:default:IdentityProviderHttpHandler",
|
||||
"@type": "IdentityProviderHttpHandler",
|
||||
"args_baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||
"args_idpPath": "/idp",
|
||||
"args_providerFactory": { "@id": "urn:solid-server:default:IdentityProviderFactory" },
|
||||
"args_converter": { "@id": "urn:solid-server:default:RepresentationConverter" },
|
||||
"args_interactionCompleter": {
|
||||
"comment": "Responsible for finishing OIDC interactions.",
|
||||
"@type": "InteractionCompleter",
|
||||
"providerFactory": { "@id": "urn:solid-server:default:IdentityProviderFactory" }
|
||||
},
|
||||
"args_errorHandler": { "@id": "urn:solid-server:default:ErrorHandler" }
|
||||
"comment": "Handles IDP input authorization. Permission reader should be set to allow all if no authorization is needed.",
|
||||
"@type": "AuthorizingHttpHandler",
|
||||
"@id": "urn:solid-server:default:IdentityProviderAuthorizingHandler",
|
||||
"args_credentialsExtractor": { "@id": "urn:solid-server:default:CredentialsExtractor" },
|
||||
"args_modesExtractor": { "@id": "urn:solid-server:default:ModesExtractor" },
|
||||
"args_authorizer": { "@id": "urn:solid-server:default:Authorizer" },
|
||||
"args_operationHandler": { "@id": "urn:solid-server:default:IdentityProviderHttpHandler" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "Handles IDP handler behaviour.",
|
||||
"@id": "urn:solid-server:default:IdentityProviderHttpHandler",
|
||||
"@type": "IdentityProviderHttpHandler",
|
||||
"args_baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||
"args_idpPath": "/idp",
|
||||
"args_providerFactory": { "@id": "urn:solid-server:default:IdentityProviderFactory" },
|
||||
"args_converter": { "@id": "urn:solid-server:default:RepresentationConverter" },
|
||||
"args_interactionCompleter": {
|
||||
"comment": "Responsible for finishing OIDC interactions.",
|
||||
"@type": "InteractionCompleter",
|
||||
"providerFactory": { "@id": "urn:solid-server:default:IdentityProviderFactory" }
|
||||
},
|
||||
"args_errorHandler": { "@id": "urn:solid-server:default:ErrorHandler" }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
"comment": "Handles all functionality on the forgot password page",
|
||||
"@id": "urn:solid-server:auth:password:ForgotPasswordRoute",
|
||||
"@type": "BasicInteractionRoute",
|
||||
"route": "^/forgotpassword/?$",
|
||||
"route": "^/forgotpassword/$",
|
||||
"viewTemplates": {
|
||||
"BasicInteractionRoute:_viewTemplates_key": "text/html",
|
||||
"BasicInteractionRoute:_viewTemplates_value": "@css:templates/identity/email-password/forgot-password.html.ejs"
|
||||
|
@ -5,7 +5,7 @@
|
||||
"comment": "Handles all functionality on the Login Page",
|
||||
"@id": "urn:solid-server:auth:password:LoginRoute",
|
||||
"@type": "BasicInteractionRoute",
|
||||
"route": "^/login/?$",
|
||||
"route": "^/login/$",
|
||||
"prompt": "login",
|
||||
"viewTemplates": {
|
||||
"BasicInteractionRoute:_viewTemplates_key": "text/html",
|
||||
|
@ -6,7 +6,7 @@
|
||||
"comment": "Handles the reset password page submission",
|
||||
"@id": "urn:solid-server:auth:password:ResetPasswordRoute",
|
||||
"@type": "BasicInteractionRoute",
|
||||
"route": "^/resetpassword(/[^/]*)?$",
|
||||
"route": "^/resetpassword/[^/]*$",
|
||||
"viewTemplates": {
|
||||
"BasicInteractionRoute:_viewTemplates_key": "text/html",
|
||||
"BasicInteractionRoute:_viewTemplates_value": "@css:templates/identity/email-password/reset-password.html.ejs"
|
||||
|
@ -5,7 +5,7 @@
|
||||
"comment": "Handles confirm requests",
|
||||
"@id": "urn:solid-server:auth:password:SessionRoute",
|
||||
"@type": "BasicInteractionRoute",
|
||||
"route": "^/confirm/?$",
|
||||
"route": "^/confirm/$",
|
||||
"prompt": "consent",
|
||||
"viewTemplates": {
|
||||
"BasicInteractionRoute:_viewTemplates_key": "text/html",
|
||||
|
@ -5,7 +5,7 @@
|
||||
"comment": "Handles all functionality on the register page",
|
||||
"@id": "urn:solid-server:auth:password:RegistrationRoute",
|
||||
"@type": "BasicInteractionRoute",
|
||||
"route": "^/register/?$",
|
||||
"route": "^/register/$",
|
||||
"viewTemplates": {
|
||||
"BasicInteractionRoute:_viewTemplates_key": "text/html",
|
||||
"BasicInteractionRoute:_viewTemplates_value": "@css:templates/identity/email-password/register.html.ejs"
|
||||
|
@ -10,7 +10,7 @@
|
||||
"@type": "UnionCredentialsExtractor",
|
||||
"extractors": [
|
||||
{ "@type": "UnsecureWebIdExtractor" },
|
||||
{ "@type": "EmptyCredentialsExtractor" }
|
||||
{ "@type": "PublicCredentialsExtractor" }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -13,7 +13,7 @@
|
||||
"@type": "UnsecureConstantCredentialsExtractor",
|
||||
"agent": "http://test.com/card#me"
|
||||
},
|
||||
{ "@type": "EmptyCredentialsExtractor" }
|
||||
{ "@type": "PublicCredentialsExtractor" }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -18,7 +18,7 @@
|
||||
{ "@type": "BearerWebIdExtractor" }
|
||||
]
|
||||
},
|
||||
{ "@type": "EmptyCredentialsExtractor" }
|
||||
{ "@type": "PublicCredentialsExtractor" }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -9,6 +9,11 @@
|
||||
"@id": "urn:solid-server:default:PermissionReader",
|
||||
"@type": "AllStaticReader",
|
||||
"allow": true
|
||||
},
|
||||
{
|
||||
"comment": "Everything is allowed, so there are no auth-specific resources.",
|
||||
"@id": "urn:solid-server:default:AuthResourceHttpHandler",
|
||||
"@type": "UnsupportedAsyncHandler"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -25,6 +25,16 @@
|
||||
},
|
||||
{ "@id": "urn:solid-server:default:WebAclReader" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"comment": "In case of WebACL authorization the ACL resources determine authorization.",
|
||||
"@id": "urn:solid-server:default:AuthResourceHttpHandler",
|
||||
"@type": "RouterHandler",
|
||||
"args_baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||
"args_targetExtractor": { "@id": "urn:solid-server:default:TargetExtractor" },
|
||||
"args_allowedMethods": [ "*" ],
|
||||
"args_allowedPathNames": [ "^/.*\\.acl$" ],
|
||||
"args_handler": { "@id": "urn:solid-server:default:LdpHandler" }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
"files-scs:config/http/middleware/websockets.json",
|
||||
"files-scs:config/http/server-factory/websockets.json",
|
||||
"files-scs:config/http/static/default.json",
|
||||
"files-scs:config/identity/access/public.json",
|
||||
"files-scs:config/identity/email/default.json",
|
||||
"files-scs:config/identity/handler/default.json",
|
||||
"files-scs:config/identity/ownership/token.json",
|
||||
|
@ -8,6 +8,7 @@
|
||||
"files-scs:config/http/middleware/websockets.json",
|
||||
"files-scs:config/http/server-factory/websockets.json",
|
||||
"files-scs:config/http/static/default.json",
|
||||
"files-scs:config/identity/access/public.json",
|
||||
"files-scs:config/identity/email/default.json",
|
||||
"files-scs:config/identity/handler/default.json",
|
||||
"files-scs:config/identity/ownership/token.json",
|
||||
|
42
config/restrict-idp.json
Normal file
42
config/restrict-idp.json
Normal file
@ -0,0 +1,42 @@
|
||||
{
|
||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld",
|
||||
"import": [
|
||||
"files-scs:config/app/main/default.json",
|
||||
"files-scs:config/app/init/default.json",
|
||||
"files-scs:config/app/setup/disabled.json",
|
||||
"files-scs:config/http/handler/default.json",
|
||||
"files-scs:config/http/middleware/websockets.json",
|
||||
"files-scs:config/http/server-factory/websockets.json",
|
||||
"files-scs:config/http/static/default.json",
|
||||
"files-scs:config/identity/access/restricted.json",
|
||||
"files-scs:config/identity/email/default.json",
|
||||
"files-scs:config/identity/handler/default.json",
|
||||
"files-scs:config/identity/ownership/token.json",
|
||||
"files-scs:config/identity/pod/static.json",
|
||||
"files-scs:config/identity/registration/enabled.json",
|
||||
"files-scs:config/ldp/authentication/dpop-bearer.json",
|
||||
"files-scs:config/ldp/authorization/webacl.json",
|
||||
"files-scs:config/ldp/handler/default.json",
|
||||
"files-scs:config/ldp/metadata-parser/default.json",
|
||||
"files-scs:config/ldp/metadata-writer/default.json",
|
||||
"files-scs:config/ldp/modes/default.json",
|
||||
"files-scs:config/storage/backend/file.json",
|
||||
"files-scs:config/storage/key-value/resource-store.json",
|
||||
"files-scs:config/storage/middleware/default.json",
|
||||
"files-scs:config/util/auxiliary/acl.json",
|
||||
"files-scs:config/util/identifiers/suffix.json",
|
||||
"files-scs:config/util/index/default.json",
|
||||
"files-scs:config/util/logging/winston.json",
|
||||
"files-scs:config/util/representation-conversion/default.json",
|
||||
"files-scs:config/util/resource-locker/memory.json",
|
||||
"files-scs:config/util/variables/default.json"
|
||||
],
|
||||
"@graph": [
|
||||
{
|
||||
"comment": [
|
||||
"This server uses a file backend and allows restricting the access to IDP components using WebACL.",
|
||||
"Make sure to read the documentation about the config/identity/access configuration."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -8,6 +8,7 @@
|
||||
"files-scs:config/http/middleware/websockets.json",
|
||||
"files-scs:config/http/server-factory/websockets.json",
|
||||
"files-scs:config/http/static/default.json",
|
||||
"files-scs:config/identity/access/public.json",
|
||||
"files-scs:config/identity/email/default.json",
|
||||
"files-scs:config/identity/handler/default.json",
|
||||
"files-scs:config/identity/ownership/token.json",
|
||||
|
@ -8,6 +8,7 @@
|
||||
"files-scs:config/http/middleware/websockets.json",
|
||||
"files-scs:config/http/server-factory/websockets.json",
|
||||
"files-scs:config/http/static/default.json",
|
||||
"files-scs:config/identity/access/public.json",
|
||||
"files-scs:config/identity/email/default.json",
|
||||
"files-scs:config/identity/handler/default.json",
|
||||
"files-scs:config/identity/ownership/token.json",
|
||||
|
@ -1,21 +0,0 @@
|
||||
import type { HttpRequest } from '../server/HttpRequest';
|
||||
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
|
||||
import { CredentialGroup } from './Credentials';
|
||||
import type { CredentialSet } from './Credentials';
|
||||
import { CredentialsExtractor } from './CredentialsExtractor';
|
||||
|
||||
/**
|
||||
* Extracts the empty credentials, indicating an unauthenticated agent.
|
||||
*/
|
||||
export class EmptyCredentialsExtractor extends CredentialsExtractor {
|
||||
public async canHandle({ headers }: HttpRequest): Promise<void> {
|
||||
const { authorization } = headers;
|
||||
if (authorization) {
|
||||
throw new NotImplementedHttpError('Unexpected Authorization scheme.');
|
||||
}
|
||||
}
|
||||
|
||||
public async handle(): Promise<CredentialSet> {
|
||||
return { [CredentialGroup.public]: {}};
|
||||
}
|
||||
}
|
12
src/authentication/PublicCredentialsExtractor.ts
Normal file
12
src/authentication/PublicCredentialsExtractor.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { CredentialGroup } from './Credentials';
|
||||
import type { CredentialSet } from './Credentials';
|
||||
import { CredentialsExtractor } from './CredentialsExtractor';
|
||||
|
||||
/**
|
||||
* Extracts the public credentials, to be used for data everyone has access to.
|
||||
*/
|
||||
export class PublicCredentialsExtractor extends CredentialsExtractor {
|
||||
public async handle(): Promise<CredentialSet> {
|
||||
return { [CredentialGroup.public]: {}};
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ export * from './authentication/BearerWebIdExtractor';
|
||||
export * from './authentication/Credentials';
|
||||
export * from './authentication/CredentialsExtractor';
|
||||
export * from './authentication/DPoPWebIdExtractor';
|
||||
export * from './authentication/EmptyCredentialsExtractor';
|
||||
export * from './authentication/PublicCredentialsExtractor';
|
||||
export * from './authentication/UnionCredentialsExtractor';
|
||||
export * from './authentication/UnsecureConstantCredentialsExtractor';
|
||||
export * from './authentication/UnsecureWebIdExtractor';
|
||||
|
@ -61,7 +61,7 @@ describe.each(configs)('A dynamic pod server with template config %s', (template
|
||||
});
|
||||
|
||||
it('creates a pod with the given config.', async(): Promise<void> => {
|
||||
const res = await fetch(`${baseUrl}idp/register`, {
|
||||
const res = await fetch(`${baseUrl}idp/register/`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(settings),
|
||||
@ -118,7 +118,7 @@ describe.each(configs)('A dynamic pod server with template config %s', (template
|
||||
|
||||
it('should not be able to create a pod with the same name.', async(): Promise<void> => {
|
||||
const newSettings = { ...settings, webId: 'http://test.com/#bob', email: 'bob@test.email' };
|
||||
const res = await fetch(`${baseUrl}idp/register`, {
|
||||
const res = await fetch(`${baseUrl}idp/register/`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(newSettings),
|
||||
|
@ -96,7 +96,7 @@ describe('A Solid server with IDP', (): void => {
|
||||
});
|
||||
|
||||
it('sends the form once to receive the registration triple.', async(): Promise<void> => {
|
||||
const res = await postForm(`${baseUrl}idp/register`, formBody);
|
||||
const res = await postForm(`${baseUrl}idp/register/`, formBody);
|
||||
expect(res.status).toBe(400);
|
||||
registrationTriple = extractRegistrationTriple(await res.text(), webId);
|
||||
});
|
||||
@ -112,7 +112,7 @@ describe('A Solid server with IDP', (): void => {
|
||||
});
|
||||
|
||||
it('sends the form again to successfully register.', async(): Promise<void> => {
|
||||
const res = await postForm(`${baseUrl}idp/register`, formBody);
|
||||
const res = await postForm(`${baseUrl}idp/register/`, formBody);
|
||||
expect(res.status).toBe(200);
|
||||
const text = await res.text();
|
||||
expect(text).toMatch(new RegExp(`your.WebID.*${webId}`, 'u'));
|
||||
@ -184,7 +184,7 @@ describe('A Solid server with IDP', (): void => {
|
||||
let nextUrl: string;
|
||||
|
||||
it('sends the corresponding email address through the form to get a mail.', async(): Promise<void> => {
|
||||
const res = await postForm(`${baseUrl}idp/forgotpassword`, stringify({ email }));
|
||||
const res = await postForm(`${baseUrl}idp/forgotpassword/`, stringify({ email }));
|
||||
expect(res.status).toBe(200);
|
||||
expect(load(await res.text())('form p').first().text().trim())
|
||||
.toBe('If your account exists, an email has been sent with a link to reset your password.');
|
||||
@ -260,7 +260,7 @@ describe('A Solid server with IDP', (): void => {
|
||||
});
|
||||
|
||||
it('sends the form once to receive the registration triple.', async(): Promise<void> => {
|
||||
const res = await postForm(`${baseUrl}idp/register`, formBody);
|
||||
const res = await postForm(`${baseUrl}idp/register/`, formBody);
|
||||
expect(res.status).toBe(400);
|
||||
registrationTriple = extractRegistrationTriple(await res.text(), webId2);
|
||||
});
|
||||
@ -276,7 +276,7 @@ describe('A Solid server with IDP', (): void => {
|
||||
});
|
||||
|
||||
it('sends the form again to successfully register.', async(): Promise<void> => {
|
||||
const res = await postForm(`${baseUrl}idp/register`, formBody);
|
||||
const res = await postForm(`${baseUrl}idp/register/`, formBody);
|
||||
expect(res.status).toBe(200);
|
||||
const text = await res.text();
|
||||
expect(text).toMatch(new RegExp(`Your new Pod.*${baseUrl}${podName}/`, 'u'));
|
||||
@ -294,7 +294,7 @@ describe('A Solid server with IDP', (): void => {
|
||||
});
|
||||
|
||||
it('sends the form to create the WebID and register.', async(): Promise<void> => {
|
||||
const res = await postForm(`${baseUrl}idp/register`, formBody);
|
||||
const res = await postForm(`${baseUrl}idp/register/`, formBody);
|
||||
expect(res.status).toBe(200);
|
||||
const text = await res.text();
|
||||
|
||||
|
114
test/integration/RestrictedIdentity.test.ts
Normal file
114
test/integration/RestrictedIdentity.test.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import { fetch } from 'cross-fetch';
|
||||
import type { App } from '../../src/init/App';
|
||||
import { joinUrl } from '../../src/util/PathUtil';
|
||||
import { getPort } from '../util/Util';
|
||||
import { getDefaultVariables, getTestConfigPath, instantiateFromConfig } from './Config';
|
||||
import { IdentityTestState } from './IdentityTestState';
|
||||
|
||||
const port = getPort('RestrictedIdentity');
|
||||
const baseUrl = `http://localhost:${port}/`;
|
||||
|
||||
// Undo the global access token verifier mock
|
||||
jest.unmock('@solid/access-token-verifier');
|
||||
|
||||
// Prevent panva/node-openid-client from emitting DraftWarning
|
||||
jest.spyOn(process, 'emitWarning').mockImplementation();
|
||||
|
||||
describe('A server with restricted IDP access', (): void => {
|
||||
let app: App;
|
||||
const settings = {
|
||||
podName: 'alice',
|
||||
email: 'alice@test.email',
|
||||
password: 'password',
|
||||
confirmPassword: 'password',
|
||||
createWebId: true,
|
||||
register: true,
|
||||
createPod: true,
|
||||
};
|
||||
const webId = joinUrl(baseUrl, 'alice/profile/card#me');
|
||||
|
||||
beforeAll(async(): Promise<void> => {
|
||||
const instances = await instantiateFromConfig(
|
||||
'urn:solid-server:test:Instances',
|
||||
getTestConfigPath('restricted-idp.json'),
|
||||
getDefaultVariables(port, baseUrl),
|
||||
) as Record<string, any>;
|
||||
({ app } = instances);
|
||||
await app.start();
|
||||
});
|
||||
|
||||
afterAll(async(): Promise<void> => {
|
||||
await app.stop();
|
||||
});
|
||||
|
||||
it('has ACL resources in the relevant containers.', async(): Promise<void> => {
|
||||
let res = await fetch(joinUrl(baseUrl, '.well-known/.acl'));
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
res = await fetch(joinUrl(baseUrl, 'idp/.acl'));
|
||||
expect(res.status).toBe(200);
|
||||
});
|
||||
|
||||
it('can create a pod.', async(): Promise<void> => {
|
||||
const res = await fetch(`${baseUrl}idp/register/`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
||||
body: JSON.stringify(settings),
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json();
|
||||
expect(body.webId).toBe(webId);
|
||||
});
|
||||
|
||||
it('can restrict registration access.', async(): Promise<void> => {
|
||||
// Only allow new WebID to register
|
||||
const restrictedAcl = `@prefix acl: <http://www.w3.org/ns/auth/acl#>.
|
||||
@prefix foaf: <http://xmlns.com/foaf/0.1/>.
|
||||
|
||||
<#authorization>
|
||||
a acl:Authorization;
|
||||
acl:agent <${webId}>;
|
||||
acl:mode acl:Read, acl:Write, acl:Control;
|
||||
acl:accessTo <./>.`;
|
||||
|
||||
let res = await fetch(`${baseUrl}idp/register/.acl`, {
|
||||
method: 'PUT',
|
||||
headers: { 'content-type': 'text/turtle' },
|
||||
body: restrictedAcl,
|
||||
});
|
||||
expect(res.status).toBe(205);
|
||||
|
||||
// Registration is now disabled
|
||||
res = await fetch(`${baseUrl}idp/register/`);
|
||||
expect(res.status).toBe(401);
|
||||
|
||||
res = await fetch(`${baseUrl}idp/register/`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
||||
body: JSON.stringify({ ...settings, email: 'bob@test.email', podName: 'bob' }),
|
||||
});
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
|
||||
it('can still access registration with the correct credentials.', async(): Promise<void> => {
|
||||
// Logging into session
|
||||
const state = new IdentityTestState(baseUrl, 'http://mockedredirect/', baseUrl);
|
||||
const url = await state.startSession();
|
||||
await state.parseLoginPage(url);
|
||||
await state.login(url, settings.email, settings.password);
|
||||
expect(state.session.info?.webId).toBe(webId);
|
||||
|
||||
// Registration still works for this WebID
|
||||
let res = await state.session.fetch(`${baseUrl}idp/register/`);
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
res = await state.session.fetch(`${baseUrl}idp/register/`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
||||
body: JSON.stringify({ ...settings, email: 'bob@test.email', podName: 'bob' }),
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json();
|
||||
expect(body.webId).toBe(joinUrl(baseUrl, 'bob/profile/card#me'));
|
||||
});
|
||||
});
|
@ -55,7 +55,7 @@ describe('A Solid server with setup', (): void => {
|
||||
|
||||
// Registration still possible
|
||||
const registerParams = { email, podName, password, confirmPassword: password, createWebId: true };
|
||||
res = await fetch(joinUrl(baseUrl, 'idp/register'), {
|
||||
res = await fetch(joinUrl(baseUrl, 'idp/register/'), {
|
||||
method: 'POST',
|
||||
headers: { accept: 'text/html', 'content-type': 'application/json' },
|
||||
body: JSON.stringify(registerParams),
|
||||
@ -83,7 +83,7 @@ describe('A Solid server with setup', (): void => {
|
||||
|
||||
// Root pod registration is never allowed
|
||||
const registerParams = { email, podName, password, confirmPassword: password, createWebId: true, rootPod: true };
|
||||
res = await fetch(joinUrl(baseUrl, 'idp/register'), {
|
||||
res = await fetch(joinUrl(baseUrl, 'idp/register/'), {
|
||||
method: 'POST',
|
||||
headers: { accept: 'text/html', 'content-type': 'application/json' },
|
||||
body: JSON.stringify(registerParams),
|
||||
|
@ -89,7 +89,7 @@ describe.each(stores)('A subdomain server with %s', (name, { storeConfig, teardo
|
||||
|
||||
describe('handling pods', (): void => {
|
||||
it('creates pods in a subdomain.', async(): Promise<void> => {
|
||||
const res = await fetch(`${baseUrl}idp/register`, {
|
||||
const res = await fetch(`${baseUrl}idp/register/`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(settings),
|
||||
@ -150,7 +150,7 @@ describe.each(stores)('A subdomain server with %s', (name, { storeConfig, teardo
|
||||
|
||||
it('should not be able to create a pod with the same name.', async(): Promise<void> => {
|
||||
const newSettings = { ...settings, webId: 'http://test.com/#bob', email: 'bob@test.email' };
|
||||
const res = await fetch(`${baseUrl}idp/register`, {
|
||||
const res = await fetch(`${baseUrl}idp/register/`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(newSettings),
|
||||
|
@ -8,6 +8,7 @@
|
||||
"files-scs:config/http/middleware/no-websockets.json",
|
||||
"files-scs:config/http/server-factory/no-websockets.json",
|
||||
"files-scs:config/http/static/default.json",
|
||||
"files-scs:config/identity/access/public.json",
|
||||
"files-scs:config/identity/handler/default.json",
|
||||
"files-scs:config/ldp/authentication/debug-auth-header.json",
|
||||
"files-scs:config/ldp/authorization/webacl.json",
|
||||
|
46
test/integration/config/restricted-idp.json
Normal file
46
test/integration/config/restricted-idp.json
Normal file
@ -0,0 +1,46 @@
|
||||
{
|
||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld",
|
||||
"import": [
|
||||
"files-scs:config/app/main/default.json",
|
||||
"files-scs:config/app/init/default.json",
|
||||
"files-scs:config/app/setup/disabled.json",
|
||||
"files-scs:config/http/handler/default.json",
|
||||
"files-scs:config/http/middleware/websockets.json",
|
||||
"files-scs:config/http/server-factory/websockets.json",
|
||||
"files-scs:config/http/static/default.json",
|
||||
"files-scs:config/identity/access/restricted.json",
|
||||
"files-scs:config/identity/email/default.json",
|
||||
"files-scs:config/identity/handler/default.json",
|
||||
"files-scs:config/identity/ownership/token.json",
|
||||
"files-scs:config/identity/pod/static.json",
|
||||
"files-scs:config/identity/registration/enabled.json",
|
||||
"files-scs:config/ldp/authentication/dpop-bearer.json",
|
||||
"files-scs:config/ldp/authorization/webacl.json",
|
||||
"files-scs:config/ldp/handler/default.json",
|
||||
"files-scs:config/ldp/metadata-parser/default.json",
|
||||
"files-scs:config/ldp/metadata-writer/default.json",
|
||||
"files-scs:config/ldp/modes/default.json",
|
||||
"files-scs:config/storage/backend/memory.json",
|
||||
"files-scs:config/storage/key-value/resource-store.json",
|
||||
"files-scs:config/storage/middleware/default.json",
|
||||
"files-scs:config/util/auxiliary/acl.json",
|
||||
"files-scs:config/util/identifiers/suffix.json",
|
||||
"files-scs:config/util/index/default.json",
|
||||
"files-scs:config/util/logging/winston.json",
|
||||
"files-scs:config/util/representation-conversion/default.json",
|
||||
"files-scs:config/util/resource-locker/memory.json",
|
||||
"files-scs:config/util/variables/default.json"
|
||||
],
|
||||
"@graph": [
|
||||
{
|
||||
"@id": "urn:solid-server:test:Instances",
|
||||
"@type": "RecordObject",
|
||||
"RecordObject:_record": [
|
||||
{
|
||||
"RecordObject:_record_key": "app",
|
||||
"RecordObject:_record_value": { "@id": "urn:solid-server:default:App" }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -8,6 +8,7 @@
|
||||
"files-scs:config/http/middleware/no-websockets.json",
|
||||
"files-scs:config/http/server-factory/no-websockets.json",
|
||||
"files-scs:config/http/static/default.json",
|
||||
"files-scs:config/identity/access/public.json",
|
||||
"files-scs:config/identity/email/default.json",
|
||||
"files-scs:config/identity/handler/default.json",
|
||||
"files-scs:config/identity/ownership/unsafe-no-check.json",
|
||||
|
@ -8,6 +8,7 @@
|
||||
"files-scs:config/http/middleware/websockets.json",
|
||||
"files-scs:config/http/server-factory/websockets.json",
|
||||
"files-scs:config/http/static/default.json",
|
||||
"files-scs:config/identity/access/public.json",
|
||||
"files-scs:config/identity/handler/default.json",
|
||||
"files-scs:config/identity/ownership/token.json",
|
||||
"files-scs:config/identity/pod/static.json",
|
||||
|
@ -8,6 +8,7 @@
|
||||
"files-scs:config/http/middleware/no-websockets.json",
|
||||
"files-scs:config/http/server-factory/no-websockets.json",
|
||||
"files-scs:config/http/static/default.json",
|
||||
"files-scs:config/identity/access/public.json",
|
||||
"files-scs:config/identity/email/default.json",
|
||||
"files-scs:config/identity/handler/default.json",
|
||||
"files-scs:config/identity/ownership/unsafe-no-check.json",
|
||||
|
@ -8,6 +8,7 @@
|
||||
"files-scs:config/http/middleware/websockets.json",
|
||||
"files-scs:config/http/server-factory/websockets.json",
|
||||
"files-scs:config/http/static/default.json",
|
||||
"files-scs:config/identity/access/public.json",
|
||||
"files-scs:config/identity/email/default.json",
|
||||
"files-scs:config/identity/handler/default.json",
|
||||
"files-scs:config/identity/ownership/token.json",
|
||||
|
@ -1,21 +0,0 @@
|
||||
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||
import { EmptyCredentialsExtractor } from '../../../src/authentication/EmptyCredentialsExtractor';
|
||||
import type { HttpRequest } from '../../../src/server/HttpRequest';
|
||||
import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError';
|
||||
|
||||
describe('An EmptyCredentialsExtractor', (): void => {
|
||||
const extractor = new EmptyCredentialsExtractor();
|
||||
|
||||
it('throws an error if an Authorization header is specified.', async(): Promise<void> => {
|
||||
const headers = { authorization: 'Other http://alice.example/card#me' };
|
||||
const result = extractor.handleSafe({ headers } as HttpRequest);
|
||||
await expect(result).rejects.toThrow(NotImplementedHttpError);
|
||||
await expect(result).rejects.toThrow('Unexpected Authorization scheme.');
|
||||
});
|
||||
|
||||
it('returns the empty credentials.', async(): Promise<void> => {
|
||||
const headers = {};
|
||||
const result = extractor.handleSafe({ headers } as HttpRequest);
|
||||
await expect(result).resolves.toEqual({ [CredentialGroup.public]: {}});
|
||||
});
|
||||
});
|
13
test/unit/authentication/PublicCredentialsExtractor.test.ts
Normal file
13
test/unit/authentication/PublicCredentialsExtractor.test.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||
import { PublicCredentialsExtractor } from '../../../src/authentication/PublicCredentialsExtractor';
|
||||
import type { HttpRequest } from '../../../src/server/HttpRequest';
|
||||
|
||||
describe('A PublicCredentialsExtractor', (): void => {
|
||||
const extractor = new PublicCredentialsExtractor();
|
||||
|
||||
it('returns the empty credentials.', async(): Promise<void> => {
|
||||
const headers = {};
|
||||
const result = extractor.handleSafe({ headers } as HttpRequest);
|
||||
await expect(result).resolves.toEqual({ [CredentialGroup.public]: {}});
|
||||
});
|
||||
});
|
@ -12,6 +12,7 @@ const portNames = [
|
||||
'Middleware',
|
||||
'PodCreation',
|
||||
'RedisResourceLocker',
|
||||
'RestrictedIdentity',
|
||||
'ServerFetch',
|
||||
'SetupMemory',
|
||||
'SparqlStorage',
|
||||
|
Loading…
x
Reference in New Issue
Block a user