feat: Create configs for server with ACP authorization

This commit is contained in:
Joachim Van Herwegen 2022-08-16 16:06:31 +02:00
parent a6409ad00d
commit db680740b5
19 changed files with 227 additions and 45 deletions

View File

@ -34,7 +34,7 @@
],
"@graph": [
{
"comment": "A Solid server that stores its resources in memory."
"comment": "A Solid server that stores its resources in memory and uses WAC for authorization."
}
]
}

View File

@ -34,7 +34,10 @@
],
"@graph": [
{
"comment": "A multi-pod server that allows for the creation of dynamic pods through pod provisioning."
"comment": [
"A Solid server that allows for the creation of dynamic pods through pod provisioning.",
"Dynamic pods each have their own Components.js configuration."
]
}
]
}

40
config/file-acp.json Normal file
View File

@ -0,0 +1,40 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^5.0.0/components/context.jsonld",
"import": [
"css:config/app/main/default.json",
"css:config/app/init/default.json",
"css:config/app/setup/required.json",
"css:config/app/variables/default.json",
"css:config/http/handler/default.json",
"css:config/http/middleware/websockets.json",
"css:config/http/server-factory/websockets.json",
"css:config/http/static/default.json",
"css:config/identity/access/public.json",
"css:config/identity/email/default.json",
"css:config/identity/handler/default.json",
"css:config/identity/ownership/token.json",
"css:config/identity/pod/static.json",
"css:config/identity/registration/enabled.json",
"css:config/ldp/authentication/dpop-bearer.json",
"css:config/ldp/authorization/acp.json",
"css:config/ldp/handler/default.json",
"css:config/ldp/metadata-parser/default.json",
"css:config/ldp/metadata-writer/default.json",
"css:config/ldp/modes/default.json",
"css:config/storage/backend/file.json",
"css:config/storage/key-value/resource-store.json",
"css:config/storage/middleware/default.json",
"css:config/util/auxiliary/acr.json",
"css:config/util/identifiers/suffix.json",
"css:config/util/index/default.json",
"css:config/util/logging/winston.json",
"css:config/util/representation-conversion/default.json",
"css:config/util/resource-locker/file.json",
"css:config/util/variables/default.json"
],
"@graph": [
{
"comment": "A Solid server that stores its resources on disk and uses ACP for authorization."
}
]
}

View File

@ -34,7 +34,10 @@
],
"@graph": [
{
"comment": "A single-pod server that stores its resources on disk."
"comment": [
"A Solid server that stores its resources on disk and uses WAC for authorization.",
"No setup is required and the root container is initialized to allow full access for everyone so make sure to change this."
]
}
]
}

View File

@ -34,7 +34,7 @@
],
"@graph": [
{
"comment": "A Solid server that stores its resources on disk."
"comment": "A Solid server that stores its resources on disk and uses WAC for authorization."
}
]
}

View File

@ -0,0 +1,45 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^5.0.0/components/context.jsonld",
"import": [
"css:config/ldp/authorization/readers/acp.json",
"css:config/ldp/authorization/readers/ownership.json"
],
"@graph": [
{
"comment": "Requests permissions on subject resources for auxiliary resources.",
"@id": "urn:solid-server:default:PermissionReader",
"@type": "AuxiliaryReader",
"auxiliaryStrategy": { "@id": "urn:solid-server:default:AuxiliaryStrategy" },
"reader": {
"@type": "UnionPermissionReader",
"readers": [
{
"comment": "This PermissionReader will be used to prevent external access to containers used for internal storage.",
"@id": "urn:solid-server:default:PathBasedReader",
"@type": "PathBasedReader",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
},
{
"@id": "urn:solid-server:default:OwnerPermissionReader",
"@type": "OwnerPermissionReader",
"authStrategy": { "@id": "urn:solid-server:default:AcrStrategy" }
},
{
"comment": "Uses Web Access Control for authorization.",
"@id": "urn:solid-server:default:WrappedAcpReader"
}
]
}
},
{
"comment": "In case of ACP authorization the ACR 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": [ "^/.*\\.acr$" ],
"args_handler": { "@id": "urn:solid-server:default:LdpHandler" }
}
]
}

View File

@ -16,8 +16,8 @@
{
"comment": "Reinterprets Control permissions as Read/Write on the ACL document.",
"@id": "urn:solid-server:default:WebAclAuxiliaryReader",
"@type": "WebAclAuxiliaryReader",
"aclStrategy": { "@id": "urn:solid-server:default:AclStrategy" },
"@type": "AuthAuxiliaryReader",
"authStrategy": { "@id": "urn:solid-server:default:AclStrategy" },
"reader": { "@id": "urn:solid-server:default:WebAclReader" }
},
{

View File

@ -0,0 +1,27 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^5.0.0/components/context.jsonld",
"@graph": [
{
"comment": "Adds parent container checks needed for create/delete permissions.",
"@id": "urn:solid-server:default:WrappedAcpReader",
"@type": "ParentContainerReader",
"identifierStrategy": { "@id": "urn:solid-server:default:IdentifierStrategy" },
"reader": { "@id": "urn:solid-server:default:AcrAuxiliaryReader" }
},
{
"comment": "Reinterprets Control permissions as Read/Write on the ACR document.",
"@id": "urn:solid-server:default:AcrAuxiliaryReader",
"@type": "AuthAuxiliaryReader",
"authStrategy": { "@id": "urn:solid-server:default:AcrStrategy" },
"reader": { "@id": "urn:solid-server:default:AcpReader" }
},
{
"comment": "Reads out permissions from ACR documents for subject resources.",
"@id": "urn:solid-server:default:AcpReader",
"@type": "AcpReader",
"acrStrategy": { "@id": "urn:solid-server:default:AcrStrategy" },
"acrStore": { "@id": "urn:solid-server:default:ResourceStore" },
"identifierStrategy": { "@id": "urn:solid-server:default:IdentifierStrategy" }
}
]
}

View File

@ -6,7 +6,6 @@
"@id": "urn:solid-server:default:OwnerPermissionReader",
"@type": "OwnerPermissionReader",
"accountStore": { "@id": "urn:solid-server:auth:password:AccountStore" },
"aclStrategy": { "@id": "urn:solid-server:default:AclStrategy" },
"identifierStrategy": { "@id": "urn:solid-server:default:IdentifierStrategy" }
}
]

View File

@ -19,7 +19,11 @@
"@type": "PathBasedReader",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
},
{ "@id": "urn:solid-server:default:OwnerPermissionReader" },
{
"@id": "urn:solid-server:default:OwnerPermissionReader",
"@type": "OwnerPermissionReader",
"authStrategy": { "@id": "urn:solid-server:default:AclStrategy" }
},
{
"comment": "Uses Web Access Control for authorization.",
"@id": "urn:solid-server:default:WrappedWebAclReader"

View File

@ -35,8 +35,9 @@
"@graph": [
{
"comment": [
"A single-pod server that stores its resources in a SPARQL endpoint.",
"This server only supports RDF data. For this reason it can not use its resource store for internal key/value storage."
"A single-pod server that stores its resources in a SPARQL endpoint and uses WAC for authorization.",
"This server only supports RDF data. For this reason it can not use its resource store for internal key/value storage.",
"No setup is required and the root container is initialized to allow full access for everyone so make sure to change this."
]
}
]

View File

@ -35,7 +35,7 @@
"@graph": [
{
"comment": [
"A Solid server that stores its resources in a SPARQL endpoint.",
"A Solid server that stores its resources in a SPARQL endpoint and uses WAC for authorization.",
"This server only supports RDF data. For this reason it can not use its resource store for internal key/value storage."
]
}

View File

@ -0,0 +1,18 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^5.0.0/components/context.jsonld",
"import": [
"css:config/util/auxiliary/strategies/acr.json",
"css:config/util/auxiliary/strategies/meta.json"
],
"@graph": [
{
"comment": "This will contain references to all the auxiliary strategies (such as the acr one) if they are needed.",
"@id": "urn:solid-server:default:AuxiliaryStrategy",
"@type": "RoutingAuxiliaryStrategy",
"sources": [
{ "@id": "urn:solid-server:default:AcrStrategy" },
{ "@id": "urn:solid-server:default:MetadataStrategy" }
]
}
]
}

View File

@ -2,7 +2,7 @@
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^5.0.0/components/context.jsonld",
"@graph": [
{
"comment": "Contains all features of acl auxiliary resources (suffix, link header, etc.).",
"comment": "Contains all features of acl auxiliary resources (suffix, etc.).",
"@id": "urn:solid-server:default:AclStrategy",
"@type": "ComposedAuxiliaryStrategy",
"identifierStrategy": { "@id": "urn:solid-server:default:AclIdentifierStrategy" },

View File

@ -0,0 +1,37 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^5.0.0/components/context.jsonld",
"@graph": [
{
"comment": "Contains all features of acr auxiliary resources (suffix, etc.).",
"@id": "urn:solid-server:default:AcrStrategy",
"@type": "ComposedAuxiliaryStrategy",
"identifierStrategy": { "@id": "urn:solid-server:default:AcrIdentifierStrategy" },
"validator": {
"@type": "RdfValidator",
"converter": { "@id": "urn:solid-server:default:RepresentationConverter" }
},
"ownAuthorization": true,
"requiredInRoot": true
},
{
"@id": "urn:solid-server:default:AcrIdentifierStrategy",
"@type": "SuffixAuxiliaryIdentifierStrategy",
"suffix": ".acr"
},
{
"comment": "Creates the Link header for the acr resources (which also use 'acl').",
"@id": "urn:solid-server:default:MetadataWriter_LinkRelAcr",
"@type": "AuxiliaryLinkMetadataWriter",
"auxiliaryStrategy": { "@id": "urn:solid-server:default:AuxiliaryStrategy" },
"specificStrategy": { "@id": "urn:solid-server:default:AcrStrategy" },
"relationType" : "acl"
},
{
"@id": "urn:solid-server:default:MetadataWriter",
"@type": "ParallelHandler",
"handlers": [
{ "@id": "urn:solid-server:default:MetadataWriter_LinkRelAcr" }
]
}
]
}

View File

@ -12,32 +12,36 @@ import type { AclPermission } from './permissions/AclPermission';
import type { AccessMap, AccessMode, PermissionMap, PermissionSet } from './permissions/Permissions';
/**
* Determines the permission for ACL auxiliary resources.
* This is done by looking for control permissions on the subject resource.
* Determines the permission for authorization resources (such as ACL or ACR).
* In contrast to the regular resource mechanism, read/write access to authorization resources
* is obtained by setting Control permissions on the corresponding subject resource
* rather than directly setting permissions for the authorization resource itself.
* Hence, this class transforms Control permissions on the subject resource
* to Read/Write permissions on the authorization resource.
*/
export class WebAclAuxiliaryReader extends PermissionReader {
export class AuthAuxiliaryReader extends PermissionReader {
protected readonly logger = getLoggerFor(this);
private readonly reader: PermissionReader;
private readonly aclStrategy: AuxiliaryStrategy;
private readonly authStrategy: AuxiliaryStrategy;
public constructor(reader: PermissionReader, aclStrategy: AuxiliaryStrategy) {
public constructor(reader: PermissionReader, authStrategy: AuxiliaryStrategy) {
super();
this.reader = reader;
this.aclStrategy = aclStrategy;
this.authStrategy = authStrategy;
}
public async handle({ requestedModes, credentials }: PermissionReaderInput): Promise<PermissionMap> {
// Finds all the ACL identifiers
const aclMap = new Map(this.findAcl(requestedModes));
const authMap = new Map(this.findAuth(requestedModes));
// Replaces the ACL identifies with the corresponding subject identifiers
const updatedMap = modify(new IdentifierSetMultiMap(requestedModes),
{ add: aclMap.values(), remove: aclMap.keys() });
{ add: authMap.values(), remove: authMap.keys() });
const result = await this.reader.handleSafe({ requestedModes: updatedMap, credentials });
// Extracts the ACL permissions based on the subject control permissions
for (const [ identifier, [ subject ]] of aclMap) {
// Extracts the permissions based on the subject control permissions
for (const [ identifier, [ subject ]] of authMap) {
this.logger.debug(`Mapping ${subject.path} control permission to all permissions for ${identifier.path}`);
result.set(identifier, this.interpretControl(identifier, result.get(subject)));
}
@ -45,12 +49,12 @@ export class WebAclAuxiliaryReader extends PermissionReader {
}
/**
* Finds all ACL identifiers and maps them to their subject identifier and the requested modes.
* Finds all authorization resource identifiers and maps them to their subject identifier and the requested modes.
*/
private* findAcl(accessMap: AccessMap): Iterable<[ResourceIdentifier, MapEntry<AccessMap>]> {
private* findAuth(accessMap: AccessMap): Iterable<[ResourceIdentifier, MapEntry<AccessMap>]> {
for (const [ identifier ] of accessMap) {
if (this.aclStrategy.isAuxiliaryIdentifier(identifier)) {
const subject = this.aclStrategy.getSubjectIdentifier(identifier);
if (this.authStrategy.isAuxiliaryIdentifier(identifier)) {
const subject = this.authStrategy.getSubjectIdentifier(identifier);
// Unfortunately there is no enum inheritance so we have to cast like this
yield [ identifier, [ subject, new Set([ AclMode.control ] as unknown as AccessMode[]) ]];
}
@ -58,19 +62,20 @@ export class WebAclAuxiliaryReader extends PermissionReader {
}
/**
* Updates the permissions for an ACL resource by interpreting the Control access mode as allowing full access.
* Updates the permissions for an authorization resource
* by interpreting the Control access mode as allowing full access.
*/
protected interpretControl(identifier: ResourceIdentifier, permissionSet: PermissionSet = {}): PermissionSet {
const aclSet: PermissionSet = {};
const authSet: PermissionSet = {};
for (const [ group, permissions ] of Object.entries(permissionSet) as [ CredentialGroup, AclPermission ][]) {
const { control } = permissions;
aclSet[group] = {
authSet[group] = {
read: control,
append: control,
write: control,
control,
} as AclPermission;
}
return aclSet;
return authSet;
}
}

View File

@ -21,23 +21,23 @@ export class OwnerPermissionReader extends PermissionReader {
protected readonly logger = getLoggerFor(this);
private readonly accountStore: AccountStore;
private readonly aclStrategy: AuxiliaryIdentifierStrategy;
private readonly authStrategy: AuxiliaryIdentifierStrategy;
private readonly identifierStrategy: IdentifierStrategy;
public constructor(accountStore: AccountStore, aclStrategy: AuxiliaryIdentifierStrategy,
public constructor(accountStore: AccountStore, authStrategy: AuxiliaryIdentifierStrategy,
identifierStrategy: IdentifierStrategy) {
super();
this.accountStore = accountStore;
this.aclStrategy = aclStrategy;
this.authStrategy = authStrategy;
this.identifierStrategy = identifierStrategy;
}
public async handle(input: PermissionReaderInput): Promise<PermissionMap> {
const result: PermissionMap = new IdentifierMap();
const requestedResources = input.requestedModes.distinctKeys();
const acls = [ ...filter(requestedResources, (id): boolean => this.aclStrategy.isAuxiliaryIdentifier(id)) ];
if (acls.length === 0) {
this.logger.debug(`No ACL resources found that need an ownership check.`);
const auths = [ ...filter(requestedResources, (id): boolean => this.authStrategy.isAuxiliaryIdentifier(id)) ];
if (auths.length === 0) {
this.logger.debug(`No authorization resources found that need an ownership check.`);
return result;
}
@ -49,10 +49,10 @@ export class OwnerPermissionReader extends PermissionReader {
return result;
}
for (const acl of acls) {
if (this.identifierStrategy.contains(podBaseUrl, acl, true)) {
this.logger.debug(`Granting Control permissions to owner on ${acl.path}`);
result.set(acl, { [CredentialGroup.agent]: {
for (const auth of auths) {
if (this.identifierStrategy.contains(podBaseUrl, auth, true)) {
this.logger.debug(`Granting Control permissions to owner on ${auth.path}`);
result.set(auth, { [CredentialGroup.agent]: {
read: true,
write: true,
append: true,

View File

@ -35,7 +35,7 @@ export * from './authorization/PathBasedReader';
export * from './authorization/PermissionBasedAuthorizer';
export * from './authorization/PermissionReader';
export * from './authorization/UnionPermissionReader';
export * from './authorization/WebAclAuxiliaryReader';
export * from './authorization/AuthAuxiliaryReader';
export * from './authorization/WebAclReader';
// HTTP/Auxiliary

View File

@ -1,16 +1,16 @@
import type { CredentialSet } from '../../../src/authentication/Credentials';
import { AuthAuxiliaryReader } from '../../../src/authorization/AuthAuxiliaryReader';
import type { PermissionReader } from '../../../src/authorization/PermissionReader';
import { AclMode } from '../../../src/authorization/permissions/AclPermission';
import type { AccessMap, PermissionMap, PermissionSet } from '../../../src/authorization/permissions/Permissions';
import { AccessMode } from '../../../src/authorization/permissions/Permissions';
import { WebAclAuxiliaryReader } from '../../../src/authorization/WebAclAuxiliaryReader';
import type { AuxiliaryStrategy } from '../../../src/http/auxiliary/AuxiliaryStrategy';
import type { ResourceIdentifier } from '../../../src/http/representation/ResourceIdentifier';
import { IdentifierMap, IdentifierSetMultiMap } from '../../../src/util/map/IdentifierMap';
import { joinUrl } from '../../../src/util/PathUtil';
import { compareMaps } from '../../util/Util';
describe('A WebAclAuxiliaryReader', (): void => {
describe('An AuthAuxiliaryReader', (): void => {
const baseUrl = 'http://example.com/';
const subject1 = { path: joinUrl(baseUrl, 'foo/') };
const acl1 = { path: joinUrl(subject1.path, '.acl') };
@ -21,7 +21,7 @@ describe('A WebAclAuxiliaryReader', (): void => {
let sourceResult: PermissionMap;
let aclStrategy: jest.Mocked<AuxiliaryStrategy>;
let source: jest.Mocked<PermissionReader>;
let reader: WebAclAuxiliaryReader;
let reader: AuthAuxiliaryReader;
beforeEach(async(): Promise<void> => {
requestedModes = new IdentifierSetMultiMap();
@ -34,7 +34,7 @@ describe('A WebAclAuxiliaryReader', (): void => {
} as any;
source = { handleSafe: jest.fn().mockResolvedValue(sourceResult) } as any;
reader = new WebAclAuxiliaryReader(source, aclStrategy);
reader = new AuthAuxiliaryReader(source, aclStrategy);
});
it('requires control permissions on the subject resource to do everything.', async(): Promise<void> => {