This library implements checking Web Access Control rules in plain Java, (... sips coffee ...).
Software using this code needs to
- define the bi-directional mapping between a
resourceand its corresponding.aclusing the interface Example - provide a map between a
resourceURI string and an its corresponding RDF dataset as aMap<String, Dataset>.
Why is the resource-.acl mapping bi-directional? Why can't we use one .acl to rule them all?
acl:Control allows modification of access control rules which have the same acl:target as the control rule.
Allowing multiple rules with different targets, i.e. allowing one .acl to be authoritative for multiple target resources makes enforcing the control right unnecessarily complicated: You can't allow HTTP PUT or DELETE, if there exist rules for other resources, and you have to do in-depth checks of HTTP PATCH to not modify rules for other resources.
You can do this. But I chose not to.
For .acl checking using the (resource - RDF dataset) map, we check if the .acl dataset is present in the map.
We then assume that the triples describing access control rules are placed in the default graph.
Example usage:
/**
* Check the rules! If webid == null, then the request is assumed to be
* unauthenticated. If webid != null, then the request is assumed to be
* authenticated.
*
* @param resource the target of the HTTP request of an agent
* @param method the HTTP action to be executed on the target resource
* @param body MUST not be null, for PATCH the body should always be
* provided, otherwise shouldnt
* @param webid if != null, it is assumed to be authenticated
* @param envResourceMap appplication environment resource map
* @param envResourceAclMap application environement resource to
* corresponding aclRDF map
* @return the URI String of the matching access control rule or
* {@code null} if none matches
*
*/
public static String checkAccessControl(String resource, String method, String body, String webid, Map<String, Dataset> envResourceMap, WacMapping envResourceAclMap) {
// get the query builder for the resource (may look for inherited rules)
WacQueryBuilder queryBuilder = WacQueryBuilder
.newBuilder(envResourceMap, envResourceAclMap)
.forRequest(resource, method, body)
.byAgent(webid);
WacQuery[] queries = queryBuilder.build();
for (WacQuery query : queries) {
String ruleMatch = query.exec();
if (ruleMatch == null) {
continue;
}
return ruleMatch;
}
return null;
}By the way, WAC does not really define behaviour for the HTTP method OPTIONS. OPTIONS is common for CORS pre-flight requests. Be sure to hanlde OPTIONS manually. Otherwise you may keep wondering why your code does not work.
Want to run an example directly? Naybe something like:
/**
* example usage
*
* @param args
*/
public static void main(String[] args) {
// mock application environment
WacMapping envResourceAclMap = new ResourceAclMap(); // ! IMPLEMENTATION DEPENDENT
Map<String, Dataset> envResourceMap = new HashMap(); // ! IMPLEMENTATION DEPENDENT
String resource = "http://localhost:8080/marmotta/ldp/test";
String acl = envResourceAclMap.getAcl(resource);
System.out.println("ACL: " + acl);
String someAcl = """
@prefix acl: <http://www.w3.org/ns/auth/acl#>
<#testAgentGroupAccess> a acl:Authorization;
acl:agent <http://example.org/webid>;
acl:accessTo <test>;
acl:mode acl:Write.
""";
// Create an RDF dataset by parsing the Turtle data
Dataset dataset = DatasetFactory.create();
Model aclRDF = dataset.getDefaultModel();
InputStream stream = new ByteArrayInputStream(someAcl.getBytes(StandardCharsets.UTF_8));
RDFDataMgr.read(aclRDF, stream, acl, Lang.TTL);
// Access the dataset
System.out.println("RDF Dataset:");
dataset.getDefaultModel().write(System.out, "TTL"); // Example: Turtle format
envResourceMap.put(acl, dataset);
// mock a HTTP request
String webid = "http://example.org/webid";
String method = "PATCH";
String body = """
@prefix solid: <http://www.w3.org/ns/solid/terms#>.
@prefix ex: <http://www.example.org/terms#>.
_:rename a solid:InsertDeletePatch;
solid:where { ?person ex:familyName \"Garcia\". };
solid:inserts { ?person ex:givenName \"Alex\". };
solid:deletes { ?person ex:givenName \"Claudia\". }.
""";
// check WAC rules
String ruleGrantingAccess = checkAccessControl(resource, method, body, webid, envResourceMap, envResourceAclMap);
// result
System.out.println("\nAccess granted? " + ((ruleGrantingAccess == null) ? "No." : "Yes: " + ruleGrantingAccess));
// happy hacking
System.exit(0);
}Sure, after cloning this repository, with Maven:
mvn package
mvn exec:java
Sure, after cloning this repository, with Maven:
mvn package
Then, find the test coverage report at target/site/jacoco/index.html.
We rely on Apache Jena for handling the RDF.