├── .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 extends T> 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 extends T> 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 |
--------------------------------------------------------------------------------