Skip to content

Commit

Permalink
Merge pull request apache#567 from afs/fuseki-misc
Browse files Browse the repository at this point in the history
JENA-1709: Password file implies logged-in users
  • Loading branch information
afs authored May 2, 2019
2 parents d1ecb43 + e7ded31 commit 581b0ca
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 62 deletions.
19 changes: 4 additions & 15 deletions jena-arq/src/main/java/org/apache/jena/riot/web/HttpOp.java
Original file line number Diff line number Diff line change
Expand Up @@ -510,28 +510,17 @@ public static void execHttpPost(String url, String contentType, String content,
HttpResponseHandler handler, HttpClient httpClient, HttpContext httpContext) {
StringEntity e = null;
try {
e = new StringEntity(content, StandardCharsets.UTF_8);
e.setContentType(contentType);
if ( content != null ) {
e = new StringEntity(content, StandardCharsets.UTF_8);
e.setContentType(contentType);
}
execHttpPost(url, e, acceptType, handler, httpClient, httpContext);
}
finally {
closeEntity(e);
}
}

//
//
// StringEntity e = null;
// try {
// e = new StringEntity(content, StandardCharsets.UTF_8);
// e.setContentType(contentType);
// return execHttpPostStream(url, e, acceptType, null, null, null) ;
// }
// finally {
// closeEntity(e);
// }
// }

/**
* Executes a HTTP POST with a request body from an input stream without
* response body with no response handling
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,7 @@

import org.apache.jena.fuseki.FusekiConfigException;
import org.apache.jena.query.* ;
import org.apache.jena.rdf.model.Literal ;
import org.apache.jena.rdf.model.Model ;
import org.apache.jena.rdf.model.RDFList;
import org.apache.jena.rdf.model.RDFNode ;
import org.apache.jena.rdf.model.Resource ;
import org.apache.jena.rdf.model.*;
import org.apache.jena.shared.JenaException;
import org.apache.jena.shared.PrefixMapping ;
import org.apache.jena.vocabulary.RDFS ;
Expand Down Expand Up @@ -107,8 +103,8 @@ public static RDFNode getOne(Resource svc, String property) {
* mixture. If the subject/property isn't present, return null, so a caller can tell
* the difference between "not present" and an empty list value.
*/
public static Collection<RDFNode> getAll(Resource svc, String property) {
ResultSet rs = FusekiBuildLib.query("SELECT * { ?svc " + property + " ?x}", svc.getModel(), "svc", svc) ;
public static Collection<RDFNode> getAll(Resource resource, String property) {
ResultSet rs = FusekiBuildLib.query("SELECT * { ?subject " + property + " ?x}", resource.getModel(), "subject", resource) ;
if ( ! rs.hasNext() )
return null;
List<RDFNode> results = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class FusekiConst {
"PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>",
"PREFIX apf: <http://jena.apache.org/ARQ/property#>",
"PREFIX afn: <http://jena.apache.org/ARQ/function#>",
"",
"") ;

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import org.apache.jena.fuseki.servlets.ServletOps ;
import org.apache.jena.web.HttpSC ;

/** Base for actions that are container and also have action on items */
/** Base for actions that are container and also have actions on items */
public abstract class ActionContainerItem extends ActionCtl {

public ActionContainerItem() { super() ; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,9 @@
public class ActionStats extends ActionContainerItem
{
public ActionStats() { super() ; }

// This does not consult the system database for dormant etc.
@Override
protected JsonValue execGetContainer(HttpAction action) {
protected JsonValue execCommonContainer(HttpAction action) {
action.log.info(format("[%d] GET stats all", action.id)) ;
return generateStats(action.getDataAccessPointRegistry()) ;
}
Expand All @@ -57,8 +56,7 @@ public static JsonObject generateStats(DataAccessPointRegistry registry) {
return builder.build().getAsObject() ;
}

@Override
protected JsonValue execGetItem(HttpAction action) {
protected JsonValue execCommonItem(HttpAction action) {
action.log.info(format("[%d] GET stats dataset %s", action.id, action.getDatasetName())) ;

JsonBuilder builder = new JsonBuilder() ;
Expand Down Expand Up @@ -185,18 +183,26 @@ private long gspValue(DataService dSrv, CounterName cn) {
return counter(dSrv, Operation.GSP_RW, cn) +
counter(dSrv, Operation.GSP_R, cn) ;
}

// We shouldn't get here - no doPost above.

@Override
protected JsonValue execPostContainer(HttpAction action) {
throw new InternalError(METHOD_POST+" container") ;
return execCommonContainer(action);
}

@Override
protected JsonValue execPostItem(HttpAction action) {
throw new InternalError(METHOD_POST+" item") ;
return execCommonItem(action);
}

@Override
protected JsonValue execGetContainer(HttpAction action) {
return execCommonContainer(action);
}

@Override
protected JsonValue execGetItem(HttpAction action) {
return execCommonItem(action);
}
}


Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.jena.atlas.logging.FmtLog;
import org.apache.jena.ext.com.google.common.collect.ArrayListMultimap;
import org.apache.jena.ext.com.google.common.collect.ListMultimap;
import org.apache.jena.fuseki.Fuseki;
Expand Down Expand Up @@ -100,11 +101,22 @@ public void addEndpoint(Operation operation, String endpointName) {
}

public void addEndpoint(Operation operation, String endpointName, AuthPolicy authPolicy) {
if ( endpointName != null && endpoints.containsKey(endpointName) ) {
Operation existing = endpoints.get(endpointName).getOperation();
FmtLog.warn(Fuseki.configLog, "Duplicate use of name '%s'. Overwriting operation %s with %s",
endpointName, operation, existing);
}
Endpoint endpoint = new Endpoint(operation, endpointName, authPolicy);
endpoints.put(endpointName, endpoint);
operations.put(operation, endpoint);
}

public void removeEndpoint(Endpoint endpoint) {
if ( endpoint.endpointName != null )
endpoints.remove(endpoint.endpointName);
operations.remove(endpoint.getOperation(), endpoint);
}

public Endpoint getEndpoint(String endpointName) {
return endpoints.get(endpointName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@

public class SPARQL_QueryDataset extends SPARQL_Query
{
public SPARQL_QueryDataset(boolean verbose) { super() ; }

public SPARQL_QueryDataset()
{ this(false) ; }
public SPARQL_QueryDataset() {}

@Override
protected void validateRequest(HttpAction action)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,12 +248,20 @@ public static class Builder {
private boolean verbose = false;
private boolean withStats = false;
private boolean withPing = false;
private AuthPolicy serverAuth = null; // Server level policy.

// Server wide authorization policy.
// Endpoints, datasets and graphs within datasets may have addition policies.
private AuthPolicy serverAuth = null;

// HTTP authentication
private String passwordFile = null;
private String realm = null;
private AuthScheme authScheme = null;

// HTTPS
private String httpsKeystore = null;
private String httpsKeystorePasswd = null;

// Other servlets to add.
private List<Pair<String, HttpServlet>> servlets = new ArrayList<>();
private List<Pair<String, Filter>> filters = new ArrayList<>();
Expand Down Expand Up @@ -687,12 +695,13 @@ private ConstraintSecurityHandler buildSecurityHandler() {

// Only valid during the build() process.
private boolean hasAuthenticationHandler = false;
private boolean hasAuthenticationUse = false;
private boolean hasDataAccessControl = false;
private boolean hasDataAccessControl = false;
private boolean authenticateUser = false;

private List<DatasetGraph> datasets = null;

private void buildStart() {
// -- Server, dataset authentication
// -- Server and dataset authentication
hasAuthenticationHandler = (passwordFile != null) || (securityHandler != null) ;

if ( realm == null )
Expand All @@ -705,25 +714,29 @@ private void buildStart() {
.anyMatch(DataAccessCtl::isAccessControlled);

// Server level.
hasAuthenticationUse = ( serverAuth != null );
authenticateUser = ( serverAuth != null );
// Dataset level.
if ( ! hasAuthenticationUse ) {
if ( ! authenticateUser ) {
// Any datasets with allowedUsers?
hasAuthenticationUse = dataAccessPoints.keys().stream()
authenticateUser = dataAccessPoints.keys().stream()
.map(name-> dataAccessPoints.get(name).getDataService())
.anyMatch(dSvc->dSvc.authPolicy() != null);
}
// Endpoint level.
if ( ! hasAuthenticationUse ) {
hasAuthenticationUse = dataAccessPoints.keys().stream()
if ( ! authenticateUser ) {
authenticateUser = dataAccessPoints.keys().stream()
.map(name-> dataAccessPoints.get(name).getDataService())
.flatMap(dSrv->dSrv.getEndpoints().stream())
.anyMatch(ep->ep.getAuthPolicy()!=null);
}

// If only a password file given, and nothing else, set the server to allowedUsers="*" (must log in).
if ( passwordFile != null && ! hasAuthenticationUse )
hasAuthenticationUse = true;
if ( passwordFile != null && ! authenticateUser ) {
authenticateUser = true;
if ( serverAuth == null )
// Set server auth to "any logged in user" if it hasn't been set otherwise.
serverAuth = Auth.ANY_USER;
}
}

private void buildFinish() {
Expand All @@ -735,13 +748,10 @@ private void buildFinish() {
/** Do some checking to make sure setup is consistent. */
private void validate() {
if ( ! hasAuthenticationHandler ) {
if ( hasAuthenticationUse ) {
Fuseki.configLog.warn("'allowedUsers' is set but there is no authentication setup (e.g. password file)");
}

if ( hasDataAccessControl ) {
Fuseki.configLog.warn("Data-level access control is configured but there is no authentication setup (e.g. password file)");
}
if ( authenticateUser )
Fuseki.configLog.warn("Authetication of users required (e.g. 'allowedUsers' is set) but there is no authentication setup (e.g. password file)");
if ( hasDataAccessControl )
Fuseki.configLog.warn("Data-level access control in the configuration but there is no authentication setup (e.g. password file)");
}
}

Expand Down Expand Up @@ -781,7 +791,7 @@ private void buildAccessControl(ServletContextHandler cxt) {
if ( serverAuth != null )
JettyLib.addPathConstraint(csh, "/*");
else {
// Find datatsets than need filters.
// Find datasets that need filters.
DataAccessPointRegistry.get(cxt.getServletContext()).forEach((name, dap)-> {
DatasetGraph dsg = dap.getDataService().getDataset();
if ( dap.getDataService().authPolicy() != null ) {
Expand Down Expand Up @@ -826,27 +836,27 @@ else if ( !contextPath.startsWith("/") )

/** Add servlets and servlet filters, including the {@link FusekiFilter} */
private void servletsAndFilters(ServletContextHandler context) {
// Fuseki dataset services filter
// First in chain.
// First in chain. Authentication.
if ( serverAuth != null ) {
Predicate<String> auth = serverAuth::isAllowed;
AuthFilter authFilter = new AuthFilter(auth);
addFilter(context, "/*", authFilter);
//JettyLib.addPathConstraint(null, contextPath);
}
// Second in chain?.
// Second in chain. Looks for any URL that starts with a dataset name.
FusekiFilter ff = new FusekiFilter();
addFilter(context, "/*", ff);

// and then any additional servlets and filters.
if ( withStats )
addServlet(context, "/$/stats", new ActionStats());
addServlet(context, "/$/stats/*", new ActionStats());
if ( withPing )
addServlet(context, "/$/ping", new ActionPing());

servlets.forEach(p->addServlet(context, p.getLeft(), p.getRight()));
// Order/place?
filters.forEach (p-> addFilter(context, p.getLeft(), p.getRight()));

// Finally, drop to state content if configured.
if ( staticContentDir != null ) {
DefaultServlet staticServlet = new DefaultServlet();
ServletHolder staticContent = new ServletHolder(staticServlet);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import static org.apache.jena.riot.web.HttpOp.execHttpDelete ;
import static org.apache.jena.riot.web.HttpOp.execHttpGet ;
import static org.apache.jena.riot.web.HttpOp.execHttpPost ;
import static org.apache.jena.riot.web.HttpOp.execHttpPostStream ;

import java.io.File ;
import java.io.IOException ;
Expand Down Expand Up @@ -255,6 +256,18 @@ public class TestAdmin extends AbstractFusekiTest {
deleteDataset(dsTest) ;
}

@Test public void stats_4() {
JsonValue v = execPostJSON(ServerCtl.urlRoot()+"$/"+opStats) ;
checkJsonStatsAll(v);
}

@Test public void stats_5() {
addTestDataset() ;
JsonValue v = execPostJSON(ServerCtl.urlRoot()+"$/"+opStats+ServerCtl.datasetPath()) ;
checkJsonStatsAll(v);
deleteDataset(dsTest) ;
}

// Sync task testing

@Test public void task_1() {
Expand Down Expand Up @@ -506,14 +519,21 @@ private static void checkJsonStatsCounters(JsonValue v) {
assertTrue(obj.hasKey("RequestsGood")) ;
assertTrue(obj.hasKey("RequestsBad")) ;
}

private static JsonValue execGetJSON(String url) {
try ( TypedInputStream in = execHttpGet(url) ) {
assertEqualsIgnoreCase(WebContent.contentTypeJSON, in.getContentType()) ;
return JSON.parse(in) ;
}
}


private static JsonValue execPostJSON(String url) {
try ( TypedInputStream in = execHttpPostStream(url, null, null, null) ) {
assertEqualsIgnoreCase(WebContent.contentTypeJSON, in.getContentType()) ;
return JSON.parse(in) ;
}
}

/*
GET /$/ping
POST /$/ping
Expand Down

0 comments on commit 581b0ca

Please sign in to comment.