From dcdb9ace1037ff70c07966e3115e5744fcf5940e Mon Sep 17 00:00:00 2001 From: Jacob Hansson Date: Tue, 22 May 2012 16:41:41 +0200 Subject: [PATCH 1/2] Consoles in webadmin can now be disabled. --- server/src/docs/dev/server-configuration.txt | 19 +++ ...onfigureEnabledManagementConsolesTest.java | 80 +++++++++++++ .../webadmin/rest/ConsoleServiceTest.java | 111 ++++++++++++++++++ .../rest/GremlinConsoleServiceTest.java | 23 ++-- .../rest/Neo4jShellConsoleSessionTest.java | 11 +- .../modules/console/ConsoleRouter.coffee | 72 ++++++++---- .../modules/console/views/ConsoleView.coffee | 4 +- .../console/views/ShellConsoleView.coffee | 4 +- .../modules/console/views/console.haml | 21 ++-- .../server/configuration/Configurator.java | 11 ++ .../webadmin/console/GremlinSession.java | 13 +- .../server/webadmin/rest/ConsoleService.java | 37 +++--- ...actory.java => ConsoleSessionFactory.java} | 3 +- .../server/webadmin/rest/RootService.java | 2 +- .../webadmin/rest/SessionFactoryImpl.java | 50 ++++++-- .../ConsoleServiceRepresentation.java | 43 +++++++ .../webadmin-html/js/lib/backbone.js | 3 +- .../resources/webadmin-html/js/lib/neo4js.js | 13 +- .../ServerRootRepresentationTest.java | 4 +- .../AbstractExclusiveServerWebadminTest.java | 51 ++++++++ .../DiscoverAvailableConsolesWebIT.java | 91 ++++++++++++++ 21 files changed, 583 insertions(+), 83 deletions(-) create mode 100644 server/src/functionaltest/java/org/neo4j/server/webadmin/rest/ConfigureEnabledManagementConsolesTest.java create mode 100644 server/src/functionaltest/java/org/neo4j/server/webadmin/rest/ConsoleServiceTest.java rename server/src/main/java/org/neo4j/server/webadmin/rest/{SessionFactory.java => ConsoleSessionFactory.java} (92%) create mode 100644 server/src/main/java/org/neo4j/server/webadmin/rest/representations/ConsoleServiceRepresentation.java create mode 100644 server/src/webtest/java/org/neo4j/server/webadmin/AbstractExclusiveServerWebadminTest.java create mode 100644 server/src/webtest/java/org/neo4j/server/webadmin/DiscoverAvailableConsolesWebIT.java diff --git a/server/src/docs/dev/server-configuration.txt b/server/src/docs/dev/server-configuration.txt index 27f9d7080..5005889af 100644 --- a/server/src/docs/dev/server-configuration.txt +++ b/server/src/docs/dev/server-configuration.txt @@ -167,3 +167,22 @@ wrapper.java.additional.3=-Xloggc:data/log/neo4j-gc.log This line is already present and needs uncommenting. Note also that logging is not directed to console ; You will find the logging statements in 'data/log/ne4j-gc.log' or whatever directory you set at the option. + +=== Disabling console types in Webadmin === + +You may, for security reasons, want to disable the Gremlin console and/or the Neo4j Shell in Webadmin. +Both of them allow arbitrary code execution, and so they could constitute a security risk if you do not trust all users of your Neo4j Server. + +In the 'conf/neo4j-server.properties' file: + +[source] +---- +# To disable both Neo4j Shell and Gremlin: +org.neo4j.server.manage.console_engines= + +# To enable only the Neo4j Shell: +org.neo4j.server.manage.console_engines=shell + +# To enable both +org.neo4j.server.manage.console_engines=gremlin,shell +---- diff --git a/server/src/functionaltest/java/org/neo4j/server/webadmin/rest/ConfigureEnabledManagementConsolesTest.java b/server/src/functionaltest/java/org/neo4j/server/webadmin/rest/ConfigureEnabledManagementConsolesTest.java new file mode 100644 index 000000000..0fad4428a --- /dev/null +++ b/server/src/functionaltest/java/org/neo4j/server/webadmin/rest/ConfigureEnabledManagementConsolesTest.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2002-2012 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.webadmin.rest; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.neo4j.server.helpers.ServerBuilder.server; + +import org.junit.After; +import org.junit.Test; +import org.neo4j.server.NeoServer; +import org.neo4j.server.configuration.Configurator; +import org.neo4j.server.rest.JaxRsResponse; +import org.neo4j.server.rest.RestRequest; +import org.neo4j.test.server.ExclusiveServerTestBase; + +public class ConfigureEnabledManagementConsolesTest extends ExclusiveServerTestBase { + + private NeoServer server; + + @After + public void stopTheServer() + { + server.stop(); + } + + @Test + public void shouldBeAbleToDisableGremlinConsole() throws Exception { + server = server().withProperty(Configurator.MANAGEMENT_CONSOLE_ENGINES, "shell").build(); + server.start(); + + assertThat(exec("g","gremlin").getStatus(), is(400)); + assertThat(exec("ls","shell").getStatus(), is(200)); + } + + @Test + public void shouldBeAbleToExplicitlySetConsolesToEnabled() throws Exception + { + server = server().withProperty(Configurator.MANAGEMENT_CONSOLE_ENGINES, "shell,gremlin").build(); + server.start(); + + assertThat(exec("g","gremlin").getStatus(), is(200)); + assertThat(exec("ls","shell").getStatus(), is(200)); + } + + + + @Test + public void gremlinAndShellConsolesShouldBeEnabledByDefault() throws Exception { + server = server().build(); + server.start(); + + assertThat(exec("g","gremlin").getStatus(), is(200)); + assertThat(exec("ls","shell").getStatus(), is(200)); + } + + private JaxRsResponse exec(String command, String engine) + { + return RestRequest.req().post(server.baseUri() + "db/manage/server/console", "{" + + "\"engine\":\""+engine+"\"," + + "\"command\":\""+command+"\\n\"}"); + } +} \ No newline at end of file diff --git a/server/src/functionaltest/java/org/neo4j/server/webadmin/rest/ConsoleServiceTest.java b/server/src/functionaltest/java/org/neo4j/server/webadmin/rest/ConsoleServiceTest.java new file mode 100644 index 000000000..e26f1d96c --- /dev/null +++ b/server/src/functionaltest/java/org/neo4j/server/webadmin/rest/ConsoleServiceTest.java @@ -0,0 +1,111 @@ +/** + * Copyright (c) 2002-2012 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.webadmin.rest; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; + +import javax.ws.rs.core.Response; + +import org.junit.Test; +import org.neo4j.server.database.Database; +import org.neo4j.server.rest.repr.OutputFormat; +import org.neo4j.server.rest.repr.formats.JsonFormat; +import org.neo4j.server.webadmin.console.ScriptSession; + +public class ConsoleServiceTest +{ + private final URI uri = URI.create( "http://peteriscool.com:6666/" ); + + @Test + public void correctRepresentation() throws URISyntaxException, UnsupportedEncodingException + { + ConsoleService consoleService = new ConsoleService( new ShellOnlyConsoleSessionFactory(), null, new OutputFormat( new JsonFormat(), uri, null ) ); + + Response consoleResponse = consoleService.getServiceDefinition(); + + assertEquals( 200, consoleResponse.getStatus() ); + String response = decode( consoleResponse ); + assertThat( response, containsString( "resources" ) ); + assertThat( response, containsString( uri.toString() ) ); + } + + @Test + public void advertisesAvailableConsoleEngines() throws URISyntaxException, UnsupportedEncodingException + { + ConsoleService consoleServiceWithJustShellEngine = new ConsoleService( new ShellOnlyConsoleSessionFactory(), null, new OutputFormat( new JsonFormat(), uri, null ) ); + + String response = decode( consoleServiceWithJustShellEngine.getServiceDefinition()); + + assertThat( response, containsString( "\"engines\" : [ \"shell\" ]" ) ); + + ConsoleService consoleServiceWithShellAndGremlin = new ConsoleService( new GremlinAndShellConsoleSessionFactory(), null, new OutputFormat( new JsonFormat(), uri, null ) ); + + response = decode( consoleServiceWithShellAndGremlin.getServiceDefinition()); + + assertThat( response, containsString( "\"engines\" : [ \"shell\", \"gremlin\" ]" ) ); + } + + private String decode( final Response response ) throws UnsupportedEncodingException + { + return new String( (byte[]) response.getEntity(), "UTF-8" ); + } + + private static class ShellOnlyConsoleSessionFactory implements ConsoleSessionFactory + { + @Override + public ScriptSession createSession(String engineName, Database database) + { + return null; + } + + @Override + public Iterable supportedEngines() + { + return new ArrayList(){{ + add("shell"); + }}; + } + } + + private static class GremlinAndShellConsoleSessionFactory implements ConsoleSessionFactory + { + @Override + public ScriptSession createSession(String engineName, Database database) + { + return null; + } + + @Override + public Iterable supportedEngines() + { + return new ArrayList(){{ + add("shell"); + add("gremlin"); + }}; + } + } +} diff --git a/server/src/functionaltest/java/org/neo4j/server/webadmin/rest/GremlinConsoleServiceTest.java b/server/src/functionaltest/java/org/neo4j/server/webadmin/rest/GremlinConsoleServiceTest.java index ec7e7df68..664a0cdc9 100644 --- a/server/src/functionaltest/java/org/neo4j/server/webadmin/rest/GremlinConsoleServiceTest.java +++ b/server/src/functionaltest/java/org/neo4j/server/webadmin/rest/GremlinConsoleServiceTest.java @@ -25,7 +25,7 @@ import java.io.UnsupportedEncodingException; import java.net.URI; -import java.net.URISyntaxException; +import java.util.ArrayList; import javax.ws.rs.core.Response; @@ -40,7 +40,7 @@ import org.neo4j.server.webadmin.console.ScriptSession; import org.neo4j.test.ImpermanentGraphDatabase; -public class GremlinConsoleServiceTest implements SessionFactory +public class GremlinConsoleServiceTest implements ConsoleSessionFactory { private ConsoleService consoleService; private Database database; @@ -76,17 +76,6 @@ public void canCreateNodesInGremlinLand() throws UnsupportedEncodingException assertEquals( 200, evaluatedGremlinResponse.getStatus() ); assertThat( response, containsString( "v[2]" ) ); } - - @Test - public void correctRepresentation() throws URISyntaxException, UnsupportedEncodingException - { - Response consoleResponse = consoleService.getServiceDefinition(); - - assertEquals( 200, consoleResponse.getStatus() ); - String response = decode( consoleResponse ); - assertThat( response, containsString( "resources" ) ); - assertThat( response, containsString( uri.toString() ) ); - } @Before public void setUp() throws Exception @@ -106,4 +95,12 @@ public ScriptSession createSession( String engineName, Database database ) { return new GremlinSession( database ); } + + @Override + public Iterable supportedEngines() + { + return new ArrayList(){{ + add("gremlin"); + }}; + } } diff --git a/server/src/functionaltest/java/org/neo4j/server/webadmin/rest/Neo4jShellConsoleSessionTest.java b/server/src/functionaltest/java/org/neo4j/server/webadmin/rest/Neo4jShellConsoleSessionTest.java index bc14d21e8..a7ce436af 100644 --- a/server/src/functionaltest/java/org/neo4j/server/webadmin/rest/Neo4jShellConsoleSessionTest.java +++ b/server/src/functionaltest/java/org/neo4j/server/webadmin/rest/Neo4jShellConsoleSessionTest.java @@ -25,6 +25,7 @@ import java.io.UnsupportedEncodingException; import java.net.URI; +import java.util.ArrayList; import java.util.List; import javax.ws.rs.core.Response; @@ -44,7 +45,7 @@ import org.neo4j.shell.ShellSettings; import org.neo4j.test.TestGraphDatabaseFactory; -public class Neo4jShellConsoleSessionTest implements SessionFactory +public class Neo4jShellConsoleSessionTest implements ConsoleSessionFactory { private static final String LN = System.getProperty("line.separator"); private ConsoleService consoleService; @@ -97,4 +98,12 @@ private List decode( final Response response ) throws UnsupportedEncodin { return (List)JsonHelper.readJson(new String( (byte[]) response.getEntity(), "UTF-8" )); } + + @Override + public Iterable supportedEngines() + { + return new ArrayList(){{ + add("shell"); + }}; + } } diff --git a/server/src/main/coffeescript/neo4j/webadmin/modules/console/ConsoleRouter.coffee b/server/src/main/coffeescript/neo4j/webadmin/modules/console/ConsoleRouter.coffee index 259f85495..d8bc660fb 100644 --- a/server/src/main/coffeescript/neo4j/webadmin/modules/console/ConsoleRouter.coffee +++ b/server/src/main/coffeescript/neo4j/webadmin/modules/console/ConsoleRouter.coffee @@ -30,10 +30,10 @@ define( class ConsoleRouter extends Router routes : - "/console/" : "console" - "/console/:type" : "console" + "/console/" : "showConsole" + "/console/:type" : "showConsole" - consoleType : "shell" + consoleType : "http" init : (appState) => @appState = appState @@ -47,29 +47,59 @@ define( @shellState = new Console(server:@appState.get("server"), lang:"shell") @httpState = new HttpConsole(server:@appState.get("server"), lang:"http") - @views = - gremlin : new GremlinConsoleView - appState : @appState - consoleState : @gremlinState - lang: "gremlin" - shell : new ShellConsoleView - appState : @appState - consoleState : @shellState - lang: "shell" - http : new HttpConsoleView - appState : @appState - consoleState : @httpState - lang: "http" + # Ask the server what console engines are available + self = this + @appState.getServer().manage.console.availableEngines (engines) -> + self.onAvailableEnginesLoaded(engines) - console : (type=false) => + showConsole : (type=false) => @saveLocation() + if type is false then type = @consoleType @consoleType = type - @appState.set( mainView : @getConsoleView(type) ) - @getConsoleView(type).focusOnInputField() - getConsoleView : (type) => - @views[type] + if @views? + if @views[type]? + view = @views[type] + else + alert "Unsupported console type: '#{type}', is it disabled in the server?." + view = @views['http'] + + @appState.set( mainView : view ) + view.focusOnInputField() + else + # Set a flag to remember to re-run this method when + # available engines has been loaded. + @renderWhenEnginesAreLoaded = true + + onAvailableEnginesLoaded : (engines) -> + engines.push('http') # HTTP is always available + @views = + http : new HttpConsoleView + appState : @appState + consoleState : @httpState + lang : "http" + engines : engines + + if _(engines).indexOf('gremlin') > -1 + @views.gremlin = new GremlinConsoleView + appState : @appState + consoleState : @gremlinState + lang : "gremlin" + engines : engines + + if _(engines).indexOf('shell') > -1 + @views.shell = new ShellConsoleView + appState : @appState + consoleState : @shellState + lang : "shell" + engines : engines + + # Use shell per default if it is available + @consoleType = "shell" + + if @renderWhenEnginesAreLoaded? + @showConsole() # # Bootstrapper SPI diff --git a/server/src/main/coffeescript/neo4j/webadmin/modules/console/views/ConsoleView.coffee b/server/src/main/coffeescript/neo4j/webadmin/modules/console/views/ConsoleView.coffee index 3108c25ef..39cd3c9b3 100644 --- a/server/src/main/coffeescript/neo4j/webadmin/modules/console/views/ConsoleView.coffee +++ b/server/src/main/coffeescript/neo4j/webadmin/modules/console/views/ConsoleView.coffee @@ -36,6 +36,7 @@ define( @appState = opts.appState @consoleState = opts.consoleState @lang = opts.lang + @availableEngines = opts.engines @consoleState.bind("change", @renderConsole) consoleKeyUp : (ev) => @@ -63,7 +64,8 @@ define( $("#console-input").putCursorAtEnd() renderConsole : ()=> - $("#console-base",@el).html consoleTemplate( + $("#console-base",@el).html consoleTemplate( + engines : @availableEngines lines : @consoleState.get "lines" prompt : @consoleState.get "prompt" showPrompt : @consoleState.get "showPrompt" diff --git a/server/src/main/coffeescript/neo4j/webadmin/modules/console/views/ShellConsoleView.coffee b/server/src/main/coffeescript/neo4j/webadmin/modules/console/views/ShellConsoleView.coffee index 63edc116c..644176709 100644 --- a/server/src/main/coffeescript/neo4j/webadmin/modules/console/views/ShellConsoleView.coffee +++ b/server/src/main/coffeescript/neo4j/webadmin/modules/console/views/ShellConsoleView.coffee @@ -29,7 +29,9 @@ define( class ShellConsoleView extends ConsoleView render : => - $(@el).html(baseTemplate(current:'shell')) + $(@el).html(baseTemplate( + current:'shell' + )) @renderConsole() return this ) diff --git a/server/src/main/coffeescript/neo4j/webadmin/modules/console/views/console.haml b/server/src/main/coffeescript/neo4j/webadmin/modules/console/views/console.haml index 87f21e9e6..e174e21ec 100644 --- a/server/src/main/coffeescript/neo4j/webadmin/modules/console/views/console.haml +++ b/server/src/main/coffeescript/neo4j/webadmin/modules/console/views/console.haml @@ -1,11 +1,16 @@ -#console-tabs - %ul.button-bar.grouped - %li - %a(href="#/console/shell" class="button #{(current == 'shell' ? 'current':'')}") Neo4j Shell - %li - %a(href="#/console/gremlin" class="button #{(current == 'gremlin' ? 'current':'')}") Gremlin - %li - %a(href="#/console/http" class="button #{(current == 'http' ? 'current':'')}") HTTP +:if engines.length > 1 + #console-tabs + %ul.button-bar.grouped + :if _(engines).indexOf('shell') > -1 + %li + %a(href="#/console/shell" class="button #{(current == 'shell' ? 'current':'')}") Neo4j Shell + :if _(engines).indexOf('gremlin') > -1 + %li + %a(href="#/console/gremlin" class="button #{(current == 'gremlin' ? 'current':'')}") Gremlin + :if _(engines).indexOf('http') > -1 + %li + %a(href="#/console/http" class="button #{(current == 'http' ? 'current':'')}") HTTP + #console.pad %ul :each line in lines diff --git a/server/src/main/java/org/neo4j/server/configuration/Configurator.java b/server/src/main/java/org/neo4j/server/configuration/Configurator.java index e8dda666b..75c94a686 100644 --- a/server/src/main/java/org/neo4j/server/configuration/Configurator.java +++ b/server/src/main/java/org/neo4j/server/configuration/Configurator.java @@ -20,7 +20,9 @@ package org.neo4j.server.configuration; import java.io.File; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Set; @@ -29,6 +31,7 @@ import org.neo4j.kernel.impl.util.StringLogger; import org.neo4j.kernel.info.DiagnosticsExtractor; import org.neo4j.kernel.info.DiagnosticsPhase; +import org.neo4j.server.webadmin.rest.SessionFactoryImpl; public interface Configurator { @@ -62,6 +65,14 @@ public interface Configurator String DEFAULT_WEB_ADMIN_PATH = "/webadmin"; String RRDB_LOCATION_PROPERTY_KEY = "org.neo4j.server.webadmin.rrdb.location"; + + String MANAGEMENT_CONSOLE_ENGINES = "org.neo4j.server.manage.console_engines"; + List DEFAULT_MANAGEMENT_CONSOLE_ENGINES = new ArrayList(){ + private static final long serialVersionUID = 6621747998288594121L; + { + add(SessionFactoryImpl.ConsoleEngineCreator.SHELL.name().toLowerCase()); + add(SessionFactoryImpl.ConsoleEngineCreator.GREMLIN.name().toLowerCase()); + }}; String THIRD_PARTY_PACKAGES_KEY = "org.neo4j.server.thirdparty_jaxrs_classes"; diff --git a/server/src/main/java/org/neo4j/server/webadmin/console/GremlinSession.java b/server/src/main/java/org/neo4j/server/webadmin/console/GremlinSession.java index 23f73677b..2a830db03 100644 --- a/server/src/main/java/org/neo4j/server/webadmin/console/GremlinSession.java +++ b/server/src/main/java/org/neo4j/server/webadmin/console/GremlinSession.java @@ -34,6 +34,7 @@ import org.neo4j.helpers.Pair; import org.neo4j.server.database.Database; import org.neo4j.server.database.DatabaseBlockedException; +import org.neo4j.server.logging.Logger; import com.tinkerpop.blueprints.pgm.TransactionalGraph; import com.tinkerpop.blueprints.pgm.impls.neo4j.Neo4jGraph; @@ -42,6 +43,8 @@ public class GremlinSession implements ScriptSession { private static final String INIT_FUNCTION = "init()"; + private static final Logger log = Logger.getLogger(GremlinSession.class); + protected GremlinWebConsole scriptEngine; private final Database database; private final IO io; @@ -99,13 +102,17 @@ public Pair evaluate( String script ) } else { - scriptEngine.execute( script ); - result = baos.toString(); - resetIO(); + try { + scriptEngine.execute( script ); + result = baos.toString(); + } finally { + resetIO(); + } } } catch ( GroovyRuntimeException ex ) { + log.error(ex); result = ex.getMessage(); } return Pair.of( result, null ); diff --git a/server/src/main/java/org/neo4j/server/webadmin/rest/ConsoleService.java b/server/src/main/java/org/neo4j/server/webadmin/rest/ConsoleService.java index f811ad5fd..a7eff88f3 100644 --- a/server/src/main/java/org/neo4j/server/webadmin/rest/ConsoleService.java +++ b/server/src/main/java/org/neo4j/server/webadmin/rest/ConsoleService.java @@ -33,7 +33,9 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import org.apache.commons.configuration.Configuration; import org.neo4j.helpers.Pair; +import org.neo4j.server.configuration.Configurator; import org.neo4j.server.database.Database; import org.neo4j.server.logging.Logger; import org.neo4j.server.rest.repr.BadInputException; @@ -44,31 +46,33 @@ import org.neo4j.server.rest.repr.RepresentationType; import org.neo4j.server.rest.repr.ValueRepresentation; import org.neo4j.server.webadmin.console.ScriptSession; -import org.neo4j.server.webadmin.rest.representations.ServiceDefinitionRepresentation; +import org.neo4j.server.webadmin.rest.representations.ConsoleServiceRepresentation; @Path( ConsoleService.SERVICE_PATH ) public class ConsoleService implements AdvertisableService { + public static final String SERVICE_PATH = "server/console"; + private static final String SERVICE_NAME = "console"; - static final String SERVICE_PATH = "server/console"; - private final SessionFactory sessionFactory; + private static final Logger log = Logger.getLogger( ConsoleService.class ); + + private final ConsoleSessionFactory sessionFactory; private final Database database; private final OutputFormat output; - public ConsoleService( SessionFactory sessionFactory, Database database, OutputFormat output ) + @SuppressWarnings("unchecked") + public ConsoleService( @Context Configuration config, @Context Database database, @Context HttpServletRequest req, @Context OutputFormat output ) { - this.sessionFactory = sessionFactory; - this.database = database; - this.output = output; + this( new SessionFactoryImpl( req.getSession( true ), config.getList(Configurator.MANAGEMENT_CONSOLE_ENGINES, Configurator.DEFAULT_MANAGEMENT_CONSOLE_ENGINES) ), database, output ); } - public ConsoleService( @Context Database database, @Context HttpServletRequest req, @Context OutputFormat output ) + public ConsoleService( ConsoleSessionFactory sessionFactory, Database database, OutputFormat output ) { - this( new SessionFactoryImpl( req.getSession( true ) ), database, output ); + this.sessionFactory = sessionFactory; + this.database = database; + this.output = output; } - Logger log = Logger.getLogger( ConsoleService.class ); - @Override public String getName() { @@ -84,8 +88,7 @@ public String getServerPath() @GET public Response getServiceDefinition() { - ServiceDefinitionRepresentation result = new ServiceDefinitionRepresentation( SERVICE_PATH ); - result.resourceUri( "exec", "" ); + ConsoleServiceRepresentation result = new ConsoleServiceRepresentation( SERVICE_PATH, sessionFactory.supportedEngines() ); return output.ok( result ); } @@ -110,7 +113,13 @@ public Response exec( @Context InputFormat input, String data ) .build(); } - ScriptSession scriptSession = getSession( args ); + ScriptSession scriptSession; + try { + scriptSession = getSession( args ); + } catch(IllegalArgumentException e) { + return output.badRequest(e); + } + log.trace( scriptSession.toString() ); try { diff --git a/server/src/main/java/org/neo4j/server/webadmin/rest/SessionFactory.java b/server/src/main/java/org/neo4j/server/webadmin/rest/ConsoleSessionFactory.java similarity index 92% rename from server/src/main/java/org/neo4j/server/webadmin/rest/SessionFactory.java rename to server/src/main/java/org/neo4j/server/webadmin/rest/ConsoleSessionFactory.java index caed3ddb3..217833e3d 100644 --- a/server/src/main/java/org/neo4j/server/webadmin/rest/SessionFactory.java +++ b/server/src/main/java/org/neo4j/server/webadmin/rest/ConsoleSessionFactory.java @@ -22,7 +22,8 @@ import org.neo4j.server.database.Database; import org.neo4j.server.webadmin.console.ScriptSession; -public interface SessionFactory +public interface ConsoleSessionFactory { ScriptSession createSession( String engineName, Database database ); + Iterable supportedEngines(); } diff --git a/server/src/main/java/org/neo4j/server/webadmin/rest/RootService.java b/server/src/main/java/org/neo4j/server/webadmin/rest/RootService.java index 551eccb37..1f5639b6c 100644 --- a/server/src/main/java/org/neo4j/server/webadmin/rest/RootService.java +++ b/server/src/main/java/org/neo4j/server/webadmin/rest/RootService.java @@ -41,7 +41,7 @@ public Response getServiceDefinition( @Context UriInfo uriInfo, @Context OutputF private AdvertisableService[] services() { - AdvertisableService console = new ConsoleService( (SessionFactory) null, null, null ); + AdvertisableService console = new ConsoleService( (ConsoleSessionFactory) null, null, null ); AdvertisableService jmx = new JmxService( null, null ); MonitorService monitor = new MonitorService( null, null ); diff --git a/server/src/main/java/org/neo4j/server/webadmin/rest/SessionFactoryImpl.java b/server/src/main/java/org/neo4j/server/webadmin/rest/SessionFactoryImpl.java index 02a6e3249..00654b4c8 100644 --- a/server/src/main/java/org/neo4j/server/webadmin/rest/SessionFactoryImpl.java +++ b/server/src/main/java/org/neo4j/server/webadmin/rest/SessionFactoryImpl.java @@ -19,6 +19,11 @@ */ package org.neo4j.server.webadmin.rest; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import javax.servlet.http.HttpSession; import org.neo4j.server.database.Database; @@ -26,30 +31,37 @@ import org.neo4j.server.webadmin.console.GremlinSession; import org.neo4j.server.webadmin.console.ScriptSession; -public class SessionFactoryImpl implements SessionFactory +public class SessionFactoryImpl implements ConsoleSessionFactory { private HttpSession httpSession; + private Map engineCreators = new HashMap(); - public SessionFactoryImpl( HttpSession httpSession ) + public SessionFactoryImpl( HttpSession httpSession, List supportedEngines ) { this.httpSession = httpSession; + + enableEngines(supportedEngines); } @Override public ScriptSession createSession( String engineName, Database database ) { - if ( engineName.equals( "shell" ) ) + engineName = engineName.toLowerCase(); + if(engineCreators.containsKey(engineName)) { -// return new CypherSession( database.graph ); - return getOrInstantiateSession( database, "shellSession", SessionCreator.SHELL ); - } - else - { - return getOrInstantiateSession( database, "consoleSession", SessionCreator.GREMLIN ); + return getOrInstantiateSession( database, engineName + "-console-session", engineCreators.get(engineName)); } + + throw new IllegalArgumentException("Unknown console engine '" + engineName + "'."); + } + + @Override + public Iterable supportedEngines() + { + return engineCreators.keySet(); } - private ScriptSession getOrInstantiateSession( Database database, String key, SessionCreator creator ) + private ScriptSession getOrInstantiateSession( Database database, String key, ConsoleEngineCreator creator ) { Object session = httpSession.getAttribute( key ); if ( session == null ) @@ -60,7 +72,7 @@ private ScriptSession getOrInstantiateSession( Database database, String key, Se return (ScriptSession) session; } - private static enum SessionCreator + public static enum ConsoleEngineCreator { GREMLIN { @@ -89,4 +101,20 @@ ScriptSession newSession( Database database ) abstract ScriptSession newSession( Database database ); } + + + private void enableEngines(List supportedEngines) + { + for(String engineName : supportedEngines) + { + for(ConsoleEngineCreator creator : EnumSet.allOf(ConsoleEngineCreator.class)) + { + if(creator.name().equalsIgnoreCase(engineName)) + { + engineCreators.put(engineName.toLowerCase(), creator); + } + } + } + } + } diff --git a/server/src/main/java/org/neo4j/server/webadmin/rest/representations/ConsoleServiceRepresentation.java b/server/src/main/java/org/neo4j/server/webadmin/rest/representations/ConsoleServiceRepresentation.java new file mode 100644 index 000000000..e8f3d8662 --- /dev/null +++ b/server/src/main/java/org/neo4j/server/webadmin/rest/representations/ConsoleServiceRepresentation.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2002-2012 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.webadmin.rest.representations; + +import org.neo4j.server.rest.repr.ListRepresentation; +import org.neo4j.server.rest.repr.MappingSerializer; + +public class ConsoleServiceRepresentation extends ServiceDefinitionRepresentation { + + private Iterable engines; + + public ConsoleServiceRepresentation(String basePath, Iterable engines) + { + super(basePath); + resourceUri( "exec", "" ); + this.engines = engines; + } + + @Override + public void serialize( MappingSerializer serializer ) + { + super.serialize(serializer); + serializer.putList("engines", ListRepresentation.string(engines)); + } + +} diff --git a/server/src/main/resources/webadmin-html/js/lib/backbone.js b/server/src/main/resources/webadmin-html/js/lib/backbone.js index 0c475d1b5..22ad9d832 100644 --- a/server/src/main/resources/webadmin-html/js/lib/backbone.js +++ b/server/src/main/resources/webadmin-html/js/lib/backbone.js @@ -879,7 +879,8 @@ if (!_ && (typeof require !== 'undefined')) _ = require('underscore')._; // For Backbone's purposes, jQuery or Zepto owns the `$` variable. - var $ = root.jQuery || root.Zepto; + // Commented out, because jQuery is sometimes not defined at this point + //var $ = root.jQuery || root.Zepto; // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable // to its previous owner. Returns a reference to this Backbone object. diff --git a/server/src/main/resources/webadmin-html/js/lib/neo4js.js b/server/src/main/resources/webadmin-html/js/lib/neo4js.js index 342a6d6f9..044b35ae2 100644 --- a/server/src/main/resources/webadmin-html/js/lib/neo4js.js +++ b/server/src/main/resources/webadmin-html/js/lib/neo4js.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002-2011 "Neo Technology," + * Copyright (c) 2010-2012 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. @@ -999,6 +999,11 @@ neo4j.services.ConsoleService=function(a){neo4j.Service.call(this,a) _.extend(neo4j.services.ConsoleService.prototype,neo4j.Service.prototype); neo4j.services.ConsoleService.prototype.exec=neo4j.Service.resourceFactory({resource:"exec",method:"POST",before:function(b,a){b({command:a[0],engine:a[1]},a[2]) }}); +neo4j.services.ConsoleService.prototype.availableEngines=function(a){this.serviceMethodPreflight(function(){function c(d){a(d.engines?d.engines:[]) +}function b(d){a(d) +}this.get("/",null,c,b) +}) +}; neo4j.services.JmxService=function(a){neo4j.Service.call(this,a); this.kernelInstance=neo4j.cachedFunction(this.kernelInstance,0,2000) }; @@ -1248,13 +1253,11 @@ return this.getServiceDefinition().then(function(b,d,c){a.web.get(b.relationship }else{b(a) }}) },getServiceDefinition:function(){if(typeof(this._serviceDefinitionPromise)==="undefined"){var a=this; -this._serviceDefinitionPromise=this.getDiscoveryDocument().then(function(c,d,b){a.web.get(c.data,function(e){d(e) -}) +this._serviceDefinitionPromise=this.getDiscoveryDocument().then(function(c,d,b){a.web.get(c.data,d,b) }) }return this._serviceDefinitionPromise },getDiscoveryDocument:function(){if(typeof(this._discoveryDocumentPromise)==="undefined"){var a=this; -this._discoveryDocumentPromise=new neo4j.Promise(function(c,b){a.web.get(a.url,function(d){c(d) -}) +this._discoveryDocumentPromise=new neo4j.Promise(function(c,b){a.web.get(a.url,c,b) }) }return this._discoveryDocumentPromise },get:function(c,b,d,a){this.web.get(this.url+c,b,d,a) diff --git a/server/src/test/java/org/neo4j/server/webadmin/rest/representations/ServerRootRepresentationTest.java b/server/src/test/java/org/neo4j/server/webadmin/rest/representations/ServerRootRepresentationTest.java index 0ce91fe42..d1c1f9fab 100644 --- a/server/src/test/java/org/neo4j/server/webadmin/rest/representations/ServerRootRepresentationTest.java +++ b/server/src/test/java/org/neo4j/server/webadmin/rest/representations/ServerRootRepresentationTest.java @@ -28,14 +28,14 @@ import org.junit.Test; import org.neo4j.server.webadmin.rest.ConsoleService; -import org.neo4j.server.webadmin.rest.SessionFactory; +import org.neo4j.server.webadmin.rest.ConsoleSessionFactory; public class ServerRootRepresentationTest { @Test public void shouldProvideAListOfServiceUris() throws Exception { - ConsoleService consoleService = new ConsoleService( (SessionFactory) null, null, null ); + ConsoleService consoleService = new ConsoleService( (ConsoleSessionFactory) null, null, null ); ServerRootRepresentation srr = new ServerRootRepresentation( new URI( "http://example.org:9999" ), consoleService ); Map> map = srr.serialize(); diff --git a/server/src/webtest/java/org/neo4j/server/webadmin/AbstractExclusiveServerWebadminTest.java b/server/src/webtest/java/org/neo4j/server/webadmin/AbstractExclusiveServerWebadminTest.java new file mode 100644 index 000000000..e3dc20fe2 --- /dev/null +++ b/server/src/webtest/java/org/neo4j/server/webadmin/AbstractExclusiveServerWebadminTest.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2002-2012 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.webadmin; + +import org.apache.commons.lang.StringUtils; +import org.neo4j.server.NeoServer; +import org.neo4j.server.webdriver.WebDriverFacade; +import org.neo4j.server.webdriver.WebadminWebdriverLibrary; +import org.neo4j.test.server.ExclusiveServerTestBase; + +public abstract class AbstractExclusiveServerWebadminTest extends ExclusiveServerTestBase { + + protected static WebadminWebdriverLibrary wl; + + private static WebDriverFacade webdriverFacade; + + public static void setupWebdriver(NeoServer server) throws Exception { + webdriverFacade = new WebDriverFacade(); + wl = new WebadminWebdriverLibrary( webdriverFacade, deriveBaseUri(server) ); + } + + public static void shutdownWebdriver() throws Exception { + webdriverFacade.quitBrowser(); + } + + private static String deriveBaseUri(NeoServer server) + { + String overrideBaseUri = System.getProperty( "webdriver.override.neo-server.baseuri" ); + if ( StringUtils.isNotEmpty( overrideBaseUri )) { + return overrideBaseUri; + } + return server.baseUri().toString(); + } +} diff --git a/server/src/webtest/java/org/neo4j/server/webadmin/DiscoverAvailableConsolesWebIT.java b/server/src/webtest/java/org/neo4j/server/webadmin/DiscoverAvailableConsolesWebIT.java new file mode 100644 index 000000000..ce7ad0c53 --- /dev/null +++ b/server/src/webtest/java/org/neo4j/server/webadmin/DiscoverAvailableConsolesWebIT.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2002-2012 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.webadmin; + +import static org.neo4j.server.helpers.ServerBuilder.server; + +import org.junit.Test; +import org.neo4j.server.NeoServer; +import org.neo4j.server.configuration.Configurator; +import org.openqa.selenium.By; + +public class DiscoverAvailableConsolesWebIT extends AbstractExclusiveServerWebadminTest { + + @Test + public void shouldShowBothGremlinAndShellIfAvailable() throws Exception { + NeoServer server = server().build(); + try { + server.start(); + setupWebdriver(server); + + wl.goToWebadminStartPage(); + wl.clickOnTab("Console"); + wl.waitForElementToAppear(By + .xpath("//div[@id='console-tabs']//a[contains(.,'Gremlin')]")); + wl.waitForElementToAppear(By + .xpath("//div[@id='console-tabs']//a[contains(.,'Neo4j Shell')]")); + + } finally { + shutdownWebdriver(); + server.stop(); + } + } + + @Test + public void shouldNotShowGremlinIfNotAvailable() throws Exception { + NeoServer server = server().withProperty(Configurator.MANAGEMENT_CONSOLE_ENGINES, "shell").build(); + try { + server.start(); + setupWebdriver(server); + + wl.goToWebadminStartPage(); + wl.clickOnTab("Console"); + wl.waitForElementToDisappear(By + .xpath("//div[@id='console-tabs']//a[contains(.,'Gremlin')]")); + wl.waitForElementToAppear(By + .xpath("//div[@id='console-tabs']//a[contains(.,'Neo4j Shell')]")); + + } finally { + shutdownWebdriver(); + server.stop(); + } + } + + @Test + public void shouldNotShowEitherShellIfBothAreDisabled() throws Exception { + NeoServer server = server().withProperty(Configurator.MANAGEMENT_CONSOLE_ENGINES, "").build(); + try { + server.start(); + setupWebdriver(server); + + wl.goToWebadminStartPage(); + wl.clickOnTab("Console"); + wl.waitForElementToDisappear(By + .xpath("//div[@id='console-tabs']//a[contains(.,'Gremlin')]")); + wl.waitForElementToDisappear(By + .xpath("//div[@id='console-tabs']//a[contains(.,'Neo4j Shell')]")); + + } finally { + shutdownWebdriver(); + server.stop(); + } + } + +} From be7214b693f755644d70664ef784dac53072ba95 Mon Sep 17 00:00:00 2001 From: Andres Taylor Date: Sat, 14 Jul 2012 20:50:10 +0200 Subject: [PATCH 2/2] Added commit message --- server/CHANGES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/CHANGES.txt b/server/CHANGES.txt index 2fc3b3bfe..c21c78c8f 100644 --- a/server/CHANGES.txt +++ b/server/CHANGES.txt @@ -1,3 +1,7 @@ +1.8 +-------------------- +o Consoles in webadmin can now be disabled. + 1.8.M06 (2012-07-06) -------------------- o Fixed issue that stopped the server from starting without the UDC-jars.