├── .gitignore ├── Gemfile ├── Gemfile.lock ├── Rakefile ├── Readme.md ├── conf ├── db-acl.properties ├── neo4j-server.properties └── neo4j.properties ├── package ├── post-install.sh └── pre-uninstall.sh ├── pom.xml └── src ├── main ├── assembly │ └── server-plugin.xml ├── java │ └── org │ │ └── neo4j │ │ └── server │ │ └── extension │ │ └── auth │ │ ├── AuthenticationExtensionInitializer.java │ │ ├── AuthenticationFilter.java │ │ ├── AuthenticationResource.java │ │ ├── AuthenticationService.java │ │ ├── MultipleAuthenticationService.java │ │ ├── SingleUserAuthenticationService.java │ │ └── TypedInjectable.java └── resources │ └── META-INF │ └── services │ └── org.neo4j.server.plugins.PluginLifecycle └── test └── java └── org └── neo4j └── server └── extension └── auth ├── TestAuthenticationService.java └── TestAuthentification.java /.gitignore: -------------------------------------------------------------------------------- 1 | tmp 2 | .idea 3 | data 4 | target 5 | .project 6 | .classpath 7 | .settings 8 | *.ipw 9 | *.ipr 10 | *.iml 11 | *.iws 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # A sample Gemfile 2 | source "https://rubygems.org" 3 | 4 | gem "fpm" 5 | gem "rake" 6 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | arr-pm (0.0.7) 5 | cabin (> 0) 6 | backports (2.6.2) 7 | cabin (0.6.0) 8 | clamp (0.3.1) 9 | fpm (0.4.29) 10 | arr-pm (~> 0.0.7) 11 | backports (= 2.6.2) 12 | cabin (>= 0.6.0) 13 | clamp (= 0.3.1) 14 | json (= 1.6.6) 15 | open4 16 | json (1.6.6) 17 | open4 (1.3.0) 18 | rake (10.0.3) 19 | 20 | PLATFORMS 21 | ruby 22 | 23 | DEPENDENCIES 24 | fpm 25 | rake 26 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler' 3 | 4 | version_minor = ENV['BUILD_NUMBER'] || 0 5 | version = "1.0-#{version_minor}" 6 | 7 | DEB_NAME='authentication-extension-1.9' 8 | DEB_NAME_VERSION="#{DEB_NAME}_#{version}_amd64" 9 | 10 | task :clean do 11 | # rm_rf 'target' 12 | end 13 | 14 | task :build do 15 | # sh 'mvn package' 16 | end 17 | 18 | task :package do 19 | rm_rf %W(target/deb target/#{DEB_NAME}.deb target/#{DEB_NAME_VERSION}.deb) 20 | mkdir_p 'target/deb/usr/share/neo4j-1.9/plugins' 21 | 22 | sh 'cd target/deb/usr/share/neo4j-1.9/plugins && unzip ../../../../../authentication-extension-1.9-1.0-SNAPSHOT-server-plugin.zip' 23 | sh 'find target/deb -name .DS_Store -delete' 24 | deps = %w( neo4j-cloud-1.9 ) 25 | sh "fpm -v #{version} -s dir -t deb -n #{DEB_NAME} -m admins@neotechnology.com #{deps.collect { |d| " -d #{d}" }} --deb-user 0 --deb-group 0 "+ 26 | "-p target/#{DEB_NAME_VERSION}.deb --post-install package/post-install.sh --pre-uninstall package/pre-uninstall.sh -C target/deb ." 27 | sh "cp target/#{DEB_NAME_VERSION}.deb target/#{DEB_NAME}.deb" 28 | end 29 | 30 | task :deploy => [:clean, :build, :package] do 31 | sh "scp -oStrictHostKeyChecking=no target/#{DEB_NAME_VERSION}.deb repo@debian.neo4j.org:/tmp" 32 | sh "ssh -oStrictHostKeyChecking=no repo@debian.neo4j.org './take-deb private /tmp/#{DEB_NAME_VERSION}.deb'" 33 | sh "ssh -oStrictHostKeyChecking=no repo@debian.neo4j.org 'rm -f /tmp/#{DEB_NAME_VERSION}.deb'" 34 | end 35 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Basic Auth-Filter-Extension for Neo4j-Server 2 | 3 | Just put the jar-file into `plugins` and add the following lines to the `conf/neo4j-server.properties` file. 4 | The username:password combination are the admin credentials, please change as appropriate. 5 | 6 | org.neo4j.server.credentials=username:password 7 | org.neo4j.server.thirdparty_jaxrs_classes=org.neo4j.server.extension.auth=/auth 8 | 9 | Manage the credentials by sending POST requests to the `http://server:port/auth` endpoint. 10 | 11 | ## List existing users 12 | 13 | GET http://server:port/auth/list 14 | 15 | returns data in the form {"user:pass":"RW", "user2:pass2":"RO"} 16 | 17 | curl --user username:password http://localhost:7474/auth/list 18 | 19 | ## Adding users with form-param: user=username:password 20 | 21 | POST http://server:port/auth/add-user-ro 22 | POST http://server:port/auth/add-user-rw 23 | 24 | returns "OK" on success 25 | 26 | curl --user username:password -d "user=username1:password1" http://localhost:7474/auth/add-user-rw 27 | curl --user username:password -d "user=username2:password2" http://localhost:7474/auth/add-user-ro 28 | 29 | ## Removing users with form-param: user=username:password 30 | 31 | POST http://server:port/auth/remove-user 32 | 33 | curl --user username:password -d "user=username2:password2" http://localhost:7474/auth/remove-user 34 | 35 | returns "OK" on success 36 | 37 | ## Download of precompiled jars 38 | 39 | * [authentication-extension-1.9-SNAPSHOT-1.0-SNAPSHOT.jar](http://dist.neo4j.org.s3.amazonaws.com/authentication-extension/authentication-extension-1.9-SNAPSHOT-1.0-SNAPSHOT.jar) 40 | * [authentication-extension-1.9-SNAPSHOT-1.0-SNAPSHOT-server-plugin.zip](http://dist.neo4j.org.s3.amazonaws.com/authentication-extension/authentication-extension-1.9-SNAPSHOT-1.0-SNAPSHOT-server-plugin.zip) 41 | * [authentication-extension/authentication-extension-2.0.4-1.0-SNAPSHOT.jar](http://dist.neo4j.org/authentication-extension/authentication-extension-2.0.4-1.0-SNAPSHOT.jar) 42 | * [authentication-extension/authentication-extension-2.1.5-1.0-SNAPSHOT.jar](http://dist.neo4j.org/authentication-extension/authentication-extension-2.1.5-1.0-SNAPSHOT.jar) 43 | -------------------------------------------------------------------------------- /conf/db-acl.properties: -------------------------------------------------------------------------------- 1 | user1\:p1 = r 2 | user2\:p1 = rw 3 | 1\:2 = rw 4 | 123\:345 = ro 5 | -------------------------------------------------------------------------------- /conf/neo4j-server.properties: -------------------------------------------------------------------------------- 1 | ################################################################ 2 | # Neo4j configuration 3 | # 4 | ################################################################ 5 | 6 | #*************************************************************** 7 | # Server configuration 8 | #*************************************************************** 9 | 10 | # location of the database directory 11 | org.neo4j.server.database.location=data/graph.db 12 | 13 | # http port (for all data, administrative, and UI access) 14 | org.neo4j.server.webserver.port=7475 15 | 16 | 17 | #***************************************************************** 18 | # Administration client configuration 19 | #***************************************************************** 20 | 21 | # location of the servers round-robin database directory 22 | org.neo4j.server.webadmin.rrdb.location=data/graph.db/../rrd 23 | 24 | # REST endpoint for the data API 25 | # Note the / in the end is mandatory 26 | org.neo4j.server.webadmin.data.uri=/db/data/ 27 | 28 | # REST endpoint of the administration API (used by Webadmin) 29 | org.neo4j.server.webadmin.management.uri=/db/manage/ 30 | 31 | # Low-level graph engine tuning file 32 | org.neo4j.server.db.tuning.properties=conf/neo4j.properties 33 | 34 | 35 | #Comma separated list of JAXRS packages contains JAXRS Resoruce, one package name for each mountpoint. 36 | #the listed package names will be loaded under the mountpoints specified, uncomment this line 37 | #to mount the org.neo4j.examples.server.unmanaged.HelloWorldResource.java from neo4j-examples 38 | #under /examples/unmanaged, resulting in a final URL of http://localhost:7474/examples/unmanaged/helloworld/{nodeId} 39 | #org.neo4j.server.thirdparty_jaxrs_classes=org.neo4j.examples.server.unmanaged=/examples/unmanaged 40 | org.neo4j.server.thirdparty_jaxrs_classes=oorg.neo4j.server.extension.auth=/admin 41 | 42 | org.neo4j.server.credentials = user:password 43 | -------------------------------------------------------------------------------- /conf/neo4j.properties: -------------------------------------------------------------------------------- 1 | # Default values for the low-level graph engine 2 | neostore.nodestore.db.mapped_memory=25M 3 | neostore.relationshipstore.db.mapped_memory=50M 4 | neostore.propertystore.db.mapped_memory=90M 5 | neostore.propertystore.db.strings.mapped_memory=130M 6 | neostore.propertystore.db.arrays.mapped_memory=130M 7 | 8 | # Enable this to be able to upgrade a store from 1.2 -> 1.3 9 | # allow_store_upgrade=true 10 | 11 | enable_remote_shell=false -------------------------------------------------------------------------------- /package/post-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -------------------------------------------------------------------------------- /package/pre-uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | org.neo4j.build 5 | parent-pom 6 | 35 7 | 8 | 9 | 10 | org.neo4j.server.authentication-extension 11 | 12 | 13 | 2.1.5 14 | 1.9 15 | 16 | 17 | 4.0.0 18 | org.neo4j.server 19 | authentication-extension 20 | jar 21 | ${neo4j.version}-1.0-SNAPSHOT 22 | authentication-extension 23 | 24 | 25 | 26 | 27 | maven-assembly-plugin 28 | 29 | 30 | src/main/assembly/server-plugin.xml 31 | 32 | 33 | 34 | 35 | make-assembly 36 | package 37 | 38 | single 39 | 40 | 41 | 42 | 43 | 44 | maven-compiler-plugin 45 | 46 | 1.7 47 | 1.7 48 | UTF-8 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.neo4j.app 57 | neo4j-server 58 | ${neo4j.version} 59 | provided 60 | 61 | 62 | 63 | 64 | junit 65 | junit 66 | 4.8.2 67 | test 68 | 69 | 70 | org.neo4j 71 | neo4j-kernel 72 | ${neo4j.version} 73 | test-jar 74 | test 75 | 76 | 77 | com.sun.jersey 78 | jersey-client 79 | ${jersey.version} 80 | test 81 | 82 | 83 | 84 | 85 | 86 | neo4j-release-repository 87 | Neo4j Maven 2 release repository 88 | http://m2.neo4j.org/releases 89 | 90 | 91 | neo4j-snapshot-repository 92 | Neo4j Maven 2 snapshot repository 93 | http://m2.neo4j.org/snapshots 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/main/assembly/server-plugin.xml: -------------------------------------------------------------------------------- 1 | 22 | 25 | server-plugin 26 | 27 | zip 28 | 29 | false 30 | 31 | 32 | 33 | / 34 | true 35 | runtime 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/main/java/org/neo4j/server/extension/auth/AuthenticationExtensionInitializer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2002-2014 "Neo Technology," 3 | * Network Engine for Objects in Lund AB [http://neotechnology.com] 4 | * 5 | * This file is part of Neo4j. 6 | * 7 | * Neo4j is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Affero General Public License as 9 | * published by the Free Software Foundation, either version 3 of the 10 | * License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License 18 | * along with this program. If not, see . 19 | */ 20 | package org.neo4j.server.extension.auth; 21 | 22 | import org.apache.commons.configuration.Configuration; 23 | import org.neo4j.graphdb.GraphDatabaseService; 24 | import org.neo4j.kernel.GraphDatabaseAPI; 25 | import org.neo4j.server.AbstractNeoServer; 26 | import org.neo4j.server.NeoServer; 27 | import org.neo4j.server.configuration.Configurator; 28 | import org.neo4j.server.configuration.ThirdPartyJaxRsPackage; 29 | import org.neo4j.server.database.Database; 30 | import org.neo4j.server.plugins.Injectable; 31 | import org.neo4j.server.plugins.SPIPluginLifecycle; 32 | import org.neo4j.server.web.WebServer; 33 | import org.slf4j.Logger; 34 | import org.slf4j.LoggerFactory; 35 | 36 | import java.util.Arrays; 37 | import java.util.Collection; 38 | 39 | import static org.neo4j.server.extension.auth.TypedInjectable.injectable; 40 | 41 | public class AuthenticationExtensionInitializer implements SPIPluginLifecycle { 42 | private static final Logger LOG = LoggerFactory.getLogger(AuthenticationExtensionInitializer.class); 43 | private AuthenticationFilter adminAuthenticationFilter; 44 | private AuthenticationFilter authenticationFilter; 45 | private WebServer webServer; 46 | private String adminPath; 47 | 48 | @Override 49 | public Collection> start(final GraphDatabaseService graphDatabaseService, final Configuration config) { 50 | throw new IllegalAccessError(); 51 | } 52 | 53 | @Override 54 | public void stop() { 55 | if (adminAuthenticationFilter != null) { 56 | webServer.removeFilter(adminAuthenticationFilter, adminPath); 57 | } 58 | if (authenticationFilter != null) { 59 | webServer.removeFilter(authenticationFilter, "/*"); 60 | } 61 | } 62 | 63 | @Override 64 | public Collection> start(final NeoServer neoServer) { 65 | if (LOG.isInfoEnabled()) LOG.info("START " + AuthenticationExtensionInitializer.class.toString()); 66 | 67 | webServer = getWebServer(neoServer); 68 | final Configurator configurator = neoServer.getConfigurator(); 69 | final Configuration configuration = neoServer.getConfiguration(); 70 | 71 | final String masterCredendials = configuration.getString("org.neo4j.server.credentials"); 72 | if (masterCredendials == null) { 73 | throw new RuntimeException("missing master-credentials in neo4j-server.properties"); 74 | } 75 | 76 | final SingleUserAuthenticationService adminAuth = new SingleUserAuthenticationService(masterCredendials); 77 | Database database = neoServer.getDatabase(); 78 | GraphDatabaseAPI graphDatabaseAPI = database.getGraph(); 79 | final MultipleAuthenticationService users = new MultipleAuthenticationService(graphDatabaseAPI); 80 | 81 | adminAuthenticationFilter = new AuthenticationFilter("neo4j-admin", adminAuth); 82 | adminPath = getMyMountpoint(configurator) + "/*"; 83 | webServer.addFilter(adminAuthenticationFilter, adminPath); 84 | 85 | authenticationFilter = new AuthenticationFilter("neo4j graphdb", users, adminAuth); 86 | webServer.addFilter(authenticationFilter, "/*"); 87 | 88 | return Arrays.>asList(injectable(users)); 89 | } 90 | 91 | private WebServer getWebServer(final NeoServer neoServer) { 92 | if (neoServer instanceof AbstractNeoServer) { 93 | return ((AbstractNeoServer) neoServer).getWebServer(); 94 | } 95 | throw new IllegalArgumentException("expected AbstractNeoServer"); 96 | } 97 | 98 | private String getMyMountpoint(final Configurator configurator) { 99 | final String packageName = getClass().getPackage().getName(); 100 | 101 | for (ThirdPartyJaxRsPackage o : configurator.getThirdpartyJaxRsPackages()) { 102 | if (o.getPackageName().equals(packageName)) { 103 | return o.getMountPoint(); 104 | } 105 | } 106 | throw new RuntimeException("unable to resolve our mountpoint?"); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/org/neo4j/server/extension/auth/AuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2002-2014 "Neo Technology," 3 | * Network Engine for Objects in Lund AB [http://neotechnology.com] 4 | * 5 | * This file is part of Neo4j. 6 | * 7 | * Neo4j is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Affero General Public License as 9 | * published by the Free Software Foundation, either version 3 of the 10 | * License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License 18 | * along with this program. If not, see . 19 | */ 20 | package org.neo4j.server.extension.auth; 21 | 22 | import sun.misc.BASE64Decoder; 23 | 24 | import javax.servlet.*; 25 | import javax.servlet.http.HttpServletRequest; 26 | import javax.servlet.http.HttpServletResponse; 27 | import java.io.IOException; 28 | 29 | /** 30 | * @author tbaum 31 | * @since 23.01.11 32 | */ 33 | public class AuthenticationFilter implements Filter { 34 | private final AuthenticationService[] authenticationService; 35 | private final String realmName; 36 | 37 | public AuthenticationFilter(final String realmName, final AuthenticationService... authenticationService) { 38 | this.authenticationService = authenticationService; 39 | this.realmName = realmName; 40 | } 41 | 42 | @Override public void init(final FilterConfig filterConfig) throws ServletException { 43 | } 44 | 45 | public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) 46 | throws ServletException, IOException { 47 | if (!(req instanceof HttpServletRequest) || !(res instanceof HttpServletResponse)) { 48 | throw new ServletException("request not allowed"); 49 | } 50 | 51 | final HttpServletRequest request = (HttpServletRequest) req; 52 | final HttpServletResponse response = (HttpServletResponse) res; 53 | 54 | final String header = request.getHeader("Authorization"); 55 | 56 | if (checkAuth(((HttpServletRequest) req).getMethod(), header)) { 57 | chain.doFilter(request, response); 58 | } else { 59 | sendAuthHeader(response); 60 | } 61 | } 62 | 63 | public void destroy() { 64 | } 65 | 66 | private boolean checkAuth(String method, String header) throws IOException { 67 | if (header == null) { 68 | return false; 69 | } 70 | 71 | final String encoded = header.substring(header.indexOf(" ") + 1); 72 | byte[] credentials = new BASE64Decoder().decodeBuffer(encoded); 73 | for (AuthenticationService service : authenticationService) { 74 | if (service.hasAccess(method, credentials)) { 75 | return true; 76 | } 77 | } 78 | return false; 79 | } 80 | 81 | private void sendAuthHeader(HttpServletResponse response) throws IOException { 82 | response.setHeader("WWW-Authenticate", "Basic realm=\"" + realmName + "\""); 83 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/org/neo4j/server/extension/auth/AuthenticationResource.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2002-2014 "Neo Technology," 3 | * Network Engine for Objects in Lund AB [http://neotechnology.com] 4 | * 5 | * This file is part of Neo4j. 6 | * 7 | * Neo4j is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Affero General Public License as 9 | * published by the Free Software Foundation, either version 3 of the 10 | * License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License 18 | * along with this program. If not, see . 19 | */ 20 | package org.neo4j.server.extension.auth; 21 | 22 | import org.codehaus.jackson.map.ObjectMapper; 23 | 24 | import javax.ws.rs.*; 25 | import javax.ws.rs.core.Context; 26 | import javax.ws.rs.core.Response; 27 | 28 | import java.io.IOException; 29 | 30 | import static javax.ws.rs.core.Response.Status.OK; 31 | import static org.neo4j.server.extension.auth.MultipleAuthenticationService.Permission.*; 32 | 33 | @Path("/") 34 | public class AuthenticationResource { 35 | 36 | private final static ObjectMapper mapper = new ObjectMapper(); 37 | private final MultipleAuthenticationService users; 38 | 39 | public AuthenticationResource(@Context MultipleAuthenticationService users) { 40 | this.users = users; 41 | } 42 | 43 | @GET @Path("/list") 44 | public Response listUsers() throws IOException { 45 | final String result = mapper.writeValueAsString(users.getUsers()); 46 | return Response.status(OK).entity(result).build(); 47 | } 48 | 49 | @POST @Path("/add-user-ro") 50 | public Response addUserRo(@FormParam("user") String user) { 51 | if (user == null) throw new IllegalArgumentException("missing parameter 'user'"); 52 | users.setPermissionForUser(user, RO); 53 | return Response.status(OK).entity("OK").build(); 54 | } 55 | 56 | @POST @Path("/add-user-rw") 57 | public Response addUserRw(@FormParam("user") String user) { 58 | if (user == null) throw new IllegalArgumentException("missing parameter 'user'"); 59 | users.setPermissionForUser(user, RW); 60 | return Response.status(OK).entity("OK").build(); 61 | 62 | } 63 | 64 | @POST @Path("/remove-user") 65 | public Response removeUser(@FormParam("user") String user) { 66 | if (user == null) throw new IllegalArgumentException("missing parameter 'user'"); 67 | users.setPermissionForUser(user, NONE); 68 | return Response.status(OK).entity("OK").build(); 69 | } 70 | } -------------------------------------------------------------------------------- /src/main/java/org/neo4j/server/extension/auth/AuthenticationService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2002-2014 "Neo Technology," 3 | * Network Engine for Objects in Lund AB [http://neotechnology.com] 4 | * 5 | * This file is part of Neo4j. 6 | * 7 | * Neo4j is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Affero General Public License as 9 | * published by the Free Software Foundation, either version 3 of the 10 | * License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License 18 | * along with this program. If not, see . 19 | */ 20 | package org.neo4j.server.extension.auth; 21 | 22 | /** 23 | * @author tbaum 24 | * @since 03.05.11 20:02 25 | */ 26 | public interface AuthenticationService { 27 | boolean hasAccess(String method, byte[] credentials); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/neo4j/server/extension/auth/MultipleAuthenticationService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2002-2014 "Neo Technology," 3 | * Network Engine for Objects in Lund AB [http://neotechnology.com] 4 | * 5 | * This file is part of Neo4j. 6 | * 7 | * Neo4j is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Affero General Public License as 9 | * published by the Free Software Foundation, either version 3 of the 10 | * License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License 18 | * along with this program. If not, see . 19 | */ 20 | package org.neo4j.server.extension.auth; 21 | 22 | import org.neo4j.graphdb.PropertyContainer; 23 | import org.neo4j.graphdb.Transaction; 24 | import org.neo4j.kernel.GraphDatabaseAPI; 25 | import org.neo4j.kernel.impl.core.GraphPropertiesImpl; 26 | import org.neo4j.kernel.impl.core.NodeManager; 27 | 28 | import java.util.HashMap; 29 | import java.util.Map; 30 | import java.util.regex.Matcher; 31 | import java.util.regex.Pattern; 32 | 33 | /** 34 | * @author tbaum 35 | * @since 16.04.11 15:38 36 | */ 37 | public class MultipleAuthenticationService implements AuthenticationService { 38 | 39 | private static final String CONFIG_PREFIX = MultipleAuthenticationService.class.getPackage().getName(); 40 | private static final Pattern USER_PATTERN = Pattern.compile(CONFIG_PREFIX + "\\.user\\.(.+?)(:.+)?"); 41 | private final GraphDatabaseAPI graph; 42 | 43 | public MultipleAuthenticationService(GraphDatabaseAPI graph) { 44 | this.graph = graph; 45 | } 46 | 47 | @Override public boolean hasAccess(String method, final byte[] credentials) { 48 | final String cred = new String(credentials); 49 | final String rights = getCredentials(cred); 50 | 51 | return isVerb(method, "PUT", "POST", "DELETE") && rights.contains("W") || 52 | isVerb(method, "GET") && rights.contains("R"); 53 | } 54 | 55 | private String getCredentials(String cred) { 56 | Transaction tx = graph.beginTx(); 57 | try { 58 | PropertyContainer properties = getGraphProperties(); 59 | String userKey = getUserKey(cred); 60 | Object result = properties.getProperty(userKey, ""); 61 | String credentials = (result instanceof Boolean) ? "" : (String) result; 62 | tx.success(); 63 | return credentials; 64 | } finally { 65 | tx.finish(); 66 | } 67 | } 68 | 69 | private PropertyContainer getGraphProperties() { 70 | NodeManager nodeManager = graph.getDependencyResolver().resolveDependency(NodeManager.class); 71 | return nodeManager.getGraphProperties(); 72 | } 73 | 74 | private String getUserKey(String cred) { 75 | return CONFIG_PREFIX + ".user." + cred; 76 | } 77 | 78 | public Map getUsers() { 79 | try (Transaction tx = graph.beginTx()) { 80 | final Map result = new HashMap(); 81 | 82 | PropertyContainer properties = getGraphProperties(); 83 | for (String key : properties.getPropertyKeys()) { 84 | Matcher matcher = USER_PATTERN.matcher(key); 85 | if (matcher.matches()) { 86 | String value = (String) properties.getProperty(key); 87 | result.put(matcher.group(1), Permission.valueOf(value)); 88 | } 89 | } 90 | tx.success(); 91 | return result; 92 | } 93 | } 94 | 95 | private boolean isVerb(String method, final String... verbs) { 96 | for (String verb : verbs) { 97 | if (verb.equalsIgnoreCase(method)) { 98 | return true; 99 | } 100 | } 101 | return false; 102 | } 103 | 104 | public void setPermissionForUser(String user, Permission permission) { 105 | Transaction transaction = graph.beginTx(); 106 | try { 107 | PropertyContainer properties = getGraphProperties(); 108 | String key = getUserKey(user); 109 | if (permission == Permission.NONE) { 110 | properties.removeProperty(key); 111 | } else { 112 | properties.setProperty(key, permission.name()); 113 | } 114 | transaction.success(); 115 | } catch (Exception e) { 116 | transaction.failure(); 117 | } finally { 118 | transaction.finish(); 119 | } 120 | } 121 | 122 | public enum Permission { 123 | NONE, RO, RW 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/org/neo4j/server/extension/auth/SingleUserAuthenticationService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2002-2014 "Neo Technology," 3 | * Network Engine for Objects in Lund AB [http://neotechnology.com] 4 | * 5 | * This file is part of Neo4j. 6 | * 7 | * Neo4j is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Affero General Public License as 9 | * published by the Free Software Foundation, either version 3 of the 10 | * License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License 18 | * along with this program. If not, see . 19 | */ 20 | package org.neo4j.server.extension.auth; 21 | 22 | /** 23 | * @author tbaum 24 | * @since 16.04.11 15:38 25 | */ 26 | public class SingleUserAuthenticationService implements AuthenticationService { 27 | 28 | private final String credentials; 29 | 30 | public SingleUserAuthenticationService(final String credentials) { 31 | this.credentials = credentials; 32 | } 33 | 34 | public boolean hasAccess(String method, final byte[] credentials) { 35 | return this.credentials.equals(new String(credentials)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/neo4j/server/extension/auth/TypedInjectable.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2002-2014 "Neo Technology," 3 | * Network Engine for Objects in Lund AB [http://neotechnology.com] 4 | * 5 | * This file is part of Neo4j. 6 | * 7 | * Neo4j is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Affero General Public License as 9 | * published by the Free Software Foundation, either version 3 of the 10 | * License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License 18 | * along with this program. If not, see . 19 | */ 20 | package org.neo4j.server.extension.auth; 21 | 22 | import org.neo4j.server.plugins.Injectable; 23 | 24 | public class TypedInjectable implements Injectable { 25 | 26 | private final T value; 27 | private final Class type; 28 | 29 | public static TypedInjectable injectable(T value) { 30 | return new TypedInjectable(value); 31 | } 32 | 33 | public static TypedInjectable injectable(T value, Class type) { 34 | return new TypedInjectable(value, type); 35 | } 36 | 37 | private TypedInjectable(T value) { 38 | this(value, (Class) value.getClass()); 39 | } 40 | 41 | private TypedInjectable(T value, Class type) { 42 | this.value = value; 43 | this.type = type; 44 | } 45 | 46 | @Override 47 | public T getValue() { 48 | return value; 49 | } 50 | 51 | @SuppressWarnings({"unchecked"}) 52 | @Override 53 | public Class getType() { 54 | return (Class) type; 55 | } 56 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/org.neo4j.server.plugins.PluginLifecycle: -------------------------------------------------------------------------------- 1 | org.neo4j.server.extension.auth.AuthenticationExtensionInitializer 2 | -------------------------------------------------------------------------------- /src/test/java/org/neo4j/server/extension/auth/TestAuthenticationService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2002-2014 "Neo Technology," 3 | * Network Engine for Objects in Lund AB [http://neotechnology.com] 4 | * 5 | * This file is part of Neo4j. 6 | * 7 | * Neo4j is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Affero General Public License as 9 | * published by the Free Software Foundation, either version 3 of the 10 | * License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License 18 | * along with this program. If not, see . 19 | */ 20 | package org.neo4j.server.extension.auth; 21 | 22 | import org.junit.Before; 23 | import org.junit.Test; 24 | import org.neo4j.graphdb.PropertyContainer; 25 | import org.neo4j.graphdb.Transaction; 26 | import org.neo4j.kernel.impl.core.GraphProperties; 27 | import org.neo4j.kernel.impl.core.NodeManager; 28 | import org.neo4j.test.ImpermanentGraphDatabase; 29 | 30 | import static org.junit.Assert.assertEquals; 31 | import static org.neo4j.helpers.collection.MapUtil.genericMap; 32 | import static org.neo4j.server.extension.auth.MultipleAuthenticationService.Permission.*; 33 | 34 | 35 | /** 36 | * @author tbaum 37 | * @since 24.11.11 38 | */ 39 | public class TestAuthenticationService { 40 | 41 | private MultipleAuthenticationService service; 42 | private ImpermanentGraphDatabase graphDatabase; 43 | 44 | @Before public void setup() { 45 | graphDatabase = new ImpermanentGraphDatabase(); 46 | service = new MultipleAuthenticationService(graphDatabase); 47 | } 48 | 49 | @Test public void testUserAddRemove() { 50 | service.setPermissionForUser("user1", RO); 51 | 52 | NodeManager nodeManager = graphDatabase.getDependencyResolver().resolveDependency(NodeManager.class); 53 | PropertyContainer properties = nodeManager.getGraphProperties(); 54 | Transaction transaction = graphDatabase.beginTx(); 55 | properties.setProperty("any other property", "should be ignored"); 56 | transaction.success(); 57 | transaction.finish(); 58 | 59 | assertEquals(genericMap("user1", RO), service.getUsers()); 60 | 61 | service.setPermissionForUser("user2", RW); 62 | assertEquals(genericMap("user1", RO, "user2", RW), service.getUsers()); 63 | 64 | service.setPermissionForUser("user2", NONE); 65 | assertEquals(genericMap("user1", RO), service.getUsers()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/org/neo4j/server/extension/auth/TestAuthentification.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2002-2014 "Neo Technology," 3 | * Network Engine for Objects in Lund AB [http://neotechnology.com] 4 | * 5 | * This file is part of Neo4j. 6 | * 7 | * Neo4j is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Affero General Public License as 9 | * published by the Free Software Foundation, either version 3 of the 10 | * License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License 18 | * along with this program. If not, see . 19 | */ 20 | package org.neo4j.server.extension.auth; 21 | 22 | import com.sun.jersey.api.client.Client; 23 | import com.sun.jersey.api.client.ClientResponse; 24 | import com.sun.jersey.api.client.UniformInterfaceException; 25 | import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; 26 | import com.sun.jersey.core.util.MultivaluedMapImpl; 27 | import org.junit.After; 28 | import org.junit.Before; 29 | import org.junit.Test; 30 | import org.neo4j.server.WrappingNeoServerBootstrapper; 31 | import org.neo4j.server.configuration.ServerConfigurator; 32 | import org.neo4j.server.configuration.ThirdPartyJaxRsPackage; 33 | import org.neo4j.test.ImpermanentGraphDatabase; 34 | 35 | import javax.ws.rs.core.MediaType; 36 | import javax.ws.rs.core.MultivaluedMap; 37 | import java.io.File; 38 | import java.io.IOException; 39 | 40 | import static junit.framework.Assert.assertEquals; 41 | import static junit.framework.Assert.fail; 42 | 43 | /** 44 | * @author tbaum 45 | * @since 31.05.11 21:11 46 | */ 47 | public class TestAuthentification { 48 | private WrappingNeoServerBootstrapper testBootstrapper; 49 | private final Client client = createClient(); 50 | 51 | private Client createClient() { 52 | return Client.create(); 53 | } 54 | 55 | private ClientResponse response; 56 | private final Client adminClient = createClient(); 57 | 58 | { 59 | adminClient.addFilter(new HTTPBasicAuthFilter("neo4j", "master")); 60 | } 61 | 62 | @Before 63 | public void setUp() throws Exception { 64 | 65 | ImpermanentGraphDatabase db = new ImpermanentGraphDatabase(); 66 | 67 | ServerConfigurator config = new ServerConfigurator(db); 68 | config.configuration().setProperty("org.neo4j.server.credentials", "neo4j:master"); 69 | config.getThirdpartyJaxRsPackages().add(new ThirdPartyJaxRsPackage("org.neo4j.server.extension.auth", "/admin")); 70 | testBootstrapper = new WrappingNeoServerBootstrapper(db, config); 71 | testBootstrapper.start(); 72 | } 73 | 74 | 75 | @After 76 | public void tearDown() { 77 | if (response!=null) response.close(); 78 | testBootstrapper.stop(); 79 | } 80 | 81 | private void delete(final File dir) { 82 | dir.deleteOnExit(); 83 | for (File file : dir.listFiles()) { 84 | if (file.isDirectory()) { 85 | delete(file); 86 | } else { 87 | file.deleteOnExit(); 88 | } 89 | } 90 | } 91 | 92 | @Test 93 | public void listNoUsers() throws Exception { 94 | response = adminClient.resource("http://localhost:7474/admin/list").accept(MediaType.APPLICATION_JSON_TYPE).get(ClientResponse.class); 95 | assertEquals(200, response.getStatus()); 96 | final String content = response.getEntity(String.class); 97 | assertEquals("{}", content); 98 | } 99 | 100 | @Test 101 | public void listAddedUsers() throws Exception { 102 | addUser("test-rw","pass",true); 103 | addUser("test-ro","pass",false); 104 | response = adminClient.resource("http://localhost:7474/admin/list").accept(MediaType.APPLICATION_JSON_TYPE).get(ClientResponse.class); 105 | assertEquals(200, response.getStatus()); 106 | final String content = response.getEntity(String.class); 107 | assertEquals("{\"test-ro\":\"RO\",\"test-rw\":\"RW\"}", content); 108 | } 109 | 110 | @Test public void expecting401() throws IOException, InterruptedException { 111 | try { 112 | client.resource("http://localhost:7474/").get(String.class); 113 | fail(); 114 | } catch (UniformInterfaceException e) { 115 | assertEquals("expecting responsecode 401", 401, e.getResponse().getStatus()); 116 | } 117 | 118 | try { 119 | client.resource("http://localhost:7474/db/data").get(String.class); 120 | fail(); 121 | } catch (UniformInterfaceException e) { 122 | assertEquals("expecting responsecode 401", 401, e.getResponse().getStatus()); 123 | } 124 | 125 | 126 | try { 127 | client.resource("http://localhost:7474/admin/add-user-ro").get(String.class); 128 | fail(); 129 | } catch (UniformInterfaceException e) { 130 | assertEquals("expecting responsecode 401", 401, e.getResponse().getStatus()); 131 | } 132 | 133 | 134 | try { 135 | client.resource("http://localhost:7474/admin/add-user-ro").get(String.class); 136 | fail(); 137 | } catch (UniformInterfaceException e) { 138 | assertEquals("expecting responsecode 401", 401, e.getResponse().getStatus()); 139 | } 140 | 141 | try { 142 | client.resource("http://localhost:7474/admin/add-user-rw").get(String.class); 143 | fail(); 144 | } catch (UniformInterfaceException e) { 145 | assertEquals("expecting responsecode 401", 401, e.getResponse().getStatus()); 146 | } 147 | 148 | try { 149 | client.resource("http://localhost:7474/admin/remove-user").get(String.class); 150 | fail(); 151 | } catch (UniformInterfaceException e) { 152 | assertEquals("expecting responsecode 401", 401, e.getResponse().getStatus()); 153 | } 154 | } 155 | 156 | private String addUser(final String user, String pass, boolean rw) { 157 | MultivaluedMap formData = new MultivaluedMapImpl(); 158 | formData.add("user", user+":"+pass); 159 | return adminClient.resource("http://localhost:7474/admin/add-user-"+(rw?"rw":"ro")).post(String.class, formData); 160 | } 161 | 162 | private String removeUser(final String user, String pass) { 163 | MultivaluedMap formData = new MultivaluedMapImpl(); 164 | formData.add("user", user+":"+pass); 165 | return adminClient.resource("http://localhost:7474/admin/remove-user").post(String.class, formData); 166 | } 167 | 168 | @Test public void addRoAndRemoveUserTest() throws IOException, InterruptedException { 169 | 170 | assertEquals("OK", addUser("test","pass",false)); 171 | 172 | Client client = createClient(); 173 | client.addFilter(new HTTPBasicAuthFilter("test", "pass")); 174 | 175 | client.resource("http://localhost:7474/").accept("application/json").get(String.class); 176 | client.resource("http://localhost:7474/db/data").get(String.class); 177 | 178 | try { 179 | client.resource("http://localhost:7474/db/data/node").post(String.class); 180 | fail(); 181 | } catch (UniformInterfaceException e) { 182 | assertEquals("expecting responsecode 401", 401, e.getResponse().getStatus()); 183 | } 184 | 185 | assertEquals("OK", removeUser("test","pass")); 186 | 187 | try { 188 | client.resource("http://localhost:7474/db/data/node").get(String.class); 189 | fail(); 190 | } catch (UniformInterfaceException e) { 191 | assertEquals("expecting responsecode 401", 401, e.getResponse().getStatus()); 192 | } 193 | 194 | try { 195 | client.resource("http://localhost:7474/db/data").get(String.class); 196 | fail(); 197 | } catch (UniformInterfaceException e) { 198 | assertEquals("expecting responsecode 401", 401, e.getResponse().getStatus()); 199 | } 200 | } 201 | 202 | @Test public void addRwAndRemoveUserTest() throws IOException, InterruptedException { 203 | Client adminClient = createClient(); 204 | adminClient.addFilter(new HTTPBasicAuthFilter("neo4j", "master")); 205 | 206 | MultivaluedMap formData = new MultivaluedMapImpl(); 207 | formData.add("user", "test:pass"); 208 | 209 | assertEquals("OK", adminClient.resource("http://localhost:7474/admin/add-user-rw").post(String.class, formData)); 210 | 211 | 212 | Client client = createClient(); 213 | client.addFilter(new HTTPBasicAuthFilter("test", "pass")); 214 | 215 | client.resource("http://localhost:7474/").accept("application/json").get(String.class); 216 | client.resource("http://localhost:7474/db/data").get(String.class); 217 | 218 | client.resource("http://localhost:7474/db/data/node").post(String.class); 219 | 220 | 221 | assertEquals("OK", adminClient.resource("http://localhost:7474/admin/remove-user").post(String.class, formData)); 222 | 223 | 224 | try { 225 | client.resource("http://localhost:7474/").get(String.class); 226 | fail(); 227 | } catch (UniformInterfaceException e) { 228 | assertEquals("expecting responsecode 401", 401, e.getResponse().getStatus()); 229 | } 230 | 231 | try { 232 | client.resource("http://localhost:7474/db/data").get(String.class); 233 | fail(); 234 | } catch (UniformInterfaceException e) { 235 | assertEquals("expecting responsecode 401", 401, e.getResponse().getStatus()); 236 | } 237 | } 238 | } 239 | --------------------------------------------------------------------------------