From cb297a03b4f411960c65babe35199134aca2e3e3 Mon Sep 17 00:00:00 2001 From: Olivia Ytterbrink Date: Fri, 1 Jul 2016 16:54:57 +0200 Subject: [PATCH] Extracted serialization and saving --- .../auth/FileRepositorySerializer.java | 92 +++++++++++++++++++ .../security/auth/FileUserRepository.java | 59 ++++-------- .../security/auth/UserSerialization.java | 42 ++------- .../auth/exception/FormatException.java | 28 ++++++ .../security/auth/UserSerializationTest.java | 4 +- .../enterprise/auth/FileRoleRepository.java | 68 ++++---------- .../enterprise/auth/RoleSerialization.java | 78 ++-------------- .../auth/RoleSerializationTest.java | 4 +- 8 files changed, 175 insertions(+), 200 deletions(-) create mode 100644 community/security/src/main/java/org/neo4j/server/security/auth/FileRepositorySerializer.java create mode 100644 community/security/src/main/java/org/neo4j/server/security/auth/exception/FormatException.java diff --git a/community/security/src/main/java/org/neo4j/server/security/auth/FileRepositorySerializer.java b/community/security/src/main/java/org/neo4j/server/security/auth/FileRepositorySerializer.java new file mode 100644 index 0000000000000..c0b59423d7a7b --- /dev/null +++ b/community/security/src/main/java/org/neo4j/server/security/auth/FileRepositorySerializer.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2002-2016 "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.security.auth; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.neo4j.server.security.auth.exception.FormatException; +import org.neo4j.string.UTF8; + +import static java.nio.file.StandardCopyOption.ATOMIC_MOVE; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; + +public abstract class FileRepositorySerializer +{ + public void saveRecordsToFile(Path recordsFile, Collection records) throws IOException + { + Path directory = recordsFile.getParent(); + if ( !Files.exists( directory ) ) + { + Files.createDirectories( directory ); + } + + Path tempFile = Files.createTempFile( directory, recordsFile.getFileName().toString() + "-", ".tmp" ); + try + { + Files.write( tempFile, serialize( records ) ); + Files.move( tempFile, recordsFile, ATOMIC_MOVE, REPLACE_EXISTING ); + } + catch ( Throwable e ) + { + Files.delete( tempFile ); + throw e; + } + } + + public List loadRecordsFromFile(Path recordsFile) throws IOException, FormatException + { + byte[] fileBytes = Files.readAllBytes( recordsFile ); + return deserializeRecords( fileBytes ); + } + + public byte[] serialize(Collection records) + { + StringBuilder sb = new StringBuilder(); + for ( S record : records ) + { + sb.append( serialize( record ) ).append( "\n" ); + } + return UTF8.encode( sb.toString() ); + } + + public List deserializeRecords( byte[] bytes ) throws FormatException + { + List out = new ArrayList<>( ); + int lineNumber = 1; + for ( String line : UTF8.decode( bytes ).split( "\n" ) ) + { + if ( line.trim().length() > 0 ) + { + out.add( deserializeRecord( line, lineNumber ) ); + } + lineNumber++; + } + return out; + } + + protected abstract String serialize( S record ); + + protected abstract S deserializeRecord( String line, int lineNumber ) throws FormatException; +} diff --git a/community/security/src/main/java/org/neo4j/server/security/auth/FileUserRepository.java b/community/security/src/main/java/org/neo4j/server/security/auth/FileUserRepository.java index a8435d10149a4..88ea9c1b4b399 100644 --- a/community/security/src/main/java/org/neo4j/server/security/auth/FileUserRepository.java +++ b/community/security/src/main/java/org/neo4j/server/security/auth/FileUserRepository.java @@ -26,6 +26,7 @@ import org.neo4j.logging.Log; import org.neo4j.logging.LogProvider; +import org.neo4j.server.security.auth.exception.FormatException; import static java.nio.file.StandardCopyOption.ATOMIC_MOVE; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; @@ -55,53 +56,27 @@ public void start() throws Throwable { if ( Files.exists( authFile ) ) { - loadUsersFromFile(); + List loadedUsers; + try + { + loadedUsers = serialization.loadRecordsFromFile( authFile ); + } catch ( FormatException e ) + { + log.error( "Failed to read authentication file \"%s\" (%s)", authFile.toAbsolutePath(), e.getMessage() ); + throw new IllegalStateException( "Failed to read authentication file: " + authFile ); + } + + users = loadedUsers; + for ( User user : users ) + { + usersByName.put( user.name(), user ); + } } } @Override protected void saveUsers() throws IOException { - saveUsersToFile(); - } - - private void saveUsersToFile() throws IOException - { - Path directory = authFile.getParent(); - if ( !Files.exists( directory ) ) - { - Files.createDirectories( directory ); - } - - Path tempFile = Files.createTempFile( directory, authFile.getFileName().toString() + "-", ".tmp" ); - try - { - Files.write( tempFile, serialization.serialize( users ) ); - Files.move( tempFile, authFile, ATOMIC_MOVE, REPLACE_EXISTING ); - } catch ( Throwable e ) - { - Files.delete( tempFile ); - throw e; - } - } - - private void loadUsersFromFile() throws IOException - { - byte[] fileBytes = Files.readAllBytes( authFile ); - List loadedUsers; - try - { - loadedUsers = serialization.deserializeUsers( fileBytes ); - } catch ( UserSerialization.FormatException e ) - { - log.error( "Failed to read authentication file \"%s\" (%s)", authFile.toAbsolutePath(), e.getMessage() ); - throw new IllegalStateException( "Failed to read authentication file: " + authFile ); - } - - users = loadedUsers; - for ( User user : users ) - { - usersByName.put( user.name(), user ); - } + serialization.saveRecordsToFile(authFile, users); } } diff --git a/community/security/src/main/java/org/neo4j/server/security/auth/UserSerialization.java b/community/security/src/main/java/org/neo4j/server/security/auth/UserSerialization.java index 29ff74553e716..5355f84329b01 100644 --- a/community/security/src/main/java/org/neo4j/server/security/auth/UserSerialization.java +++ b/community/security/src/main/java/org/neo4j/server/security/auth/UserSerialization.java @@ -23,6 +23,7 @@ import java.util.Collection; import java.util.List; +import org.neo4j.server.security.auth.exception.FormatException; import org.neo4j.string.HexString; import org.neo4j.string.UTF8; @@ -31,45 +32,13 @@ /** * Serializes user authorization and authentication data to a format similar to unix passwd files. */ -public class UserSerialization +public class UserSerialization extends FileRepositorySerializer { - public class FormatException extends Exception - { - FormatException( String message ) - { - super( message ); - } - } - private static final String userSeparator = ":"; private static final String credentialSeparator = ","; - public byte[] serialize(Collection users) - { - StringBuilder sb = new StringBuilder(); - for ( User user : users ) - { - sb.append( serialize(user) ).append( "\n" ); - } - return UTF8.encode( sb.toString() ); - } - - public List deserializeUsers( byte[] bytes ) throws FormatException - { - List out = new ArrayList<>(); - int lineNumber = 1; - for ( String line : UTF8.decode( bytes ).split( "\n" ) ) - { - if (line.trim().length() > 0) - { - out.add( deserializeUser( line, lineNumber ) ); - } - lineNumber++; - } - return out; - } - - private String serialize( User user ) + @Override + protected String serialize( User user ) { return String.join( userSeparator, user.name(), @@ -78,7 +47,8 @@ private String serialize( User user ) ); } - private User deserializeUser( String line, int lineNumber ) throws FormatException + @Override + protected User deserializeRecord( String line, int lineNumber ) throws FormatException { String[] parts = line.split( userSeparator, -1 ); if ( parts.length != 3 ) diff --git a/community/security/src/main/java/org/neo4j/server/security/auth/exception/FormatException.java b/community/security/src/main/java/org/neo4j/server/security/auth/exception/FormatException.java new file mode 100644 index 0000000000000..8f077c67983aa --- /dev/null +++ b/community/security/src/main/java/org/neo4j/server/security/auth/exception/FormatException.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2002-2016 "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.security.auth.exception; + +public class FormatException extends Exception +{ + public FormatException( String message ) + { + super( message ); + } +} diff --git a/community/security/src/test/java/org/neo4j/server/security/auth/UserSerializationTest.java b/community/security/src/test/java/org/neo4j/server/security/auth/UserSerializationTest.java index c298110c1c07f..ac6f5f5c08c5f 100644 --- a/community/security/src/test/java/org/neo4j/server/security/auth/UserSerializationTest.java +++ b/community/security/src/test/java/org/neo4j/server/security/auth/UserSerializationTest.java @@ -47,7 +47,7 @@ public void shouldSerializeAndDeserialize() throws Exception byte[] serialized = serialization.serialize( users ); // Then - assertThat( serialization.deserializeUsers( serialized ), equalTo( users ) ); + assertThat( serialization.deserializeRecords( serialized ), equalTo( users ) ); } /** @@ -65,7 +65,7 @@ public void shouldReadV1SerializationFormat() throws Exception byte[] hash2 = new byte[] { (byte) 0x0e, (byte) 0x1f, (byte) 0xff, (byte) 0xc2, (byte) 0x3e }; // When - List deserialized = serialization.deserializeUsers( UTF8.encode( + List deserialized = serialization.deserializeRecords( UTF8.encode( ("Mike:SHA-256,FE0056C37E,A543:\n" + "Steve:SHA-256,FE0056C37E,A543:nice_guy,password_change_required\n" + "Bob:SHA-256,0E1FFFC23E,34A4:password_change_required\n") ) ); diff --git a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/FileRoleRepository.java b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/FileRoleRepository.java index 5203e82948d4d..c683e22c3806f 100644 --- a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/FileRoleRepository.java +++ b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/FileRoleRepository.java @@ -27,6 +27,7 @@ import org.neo4j.logging.Log; import org.neo4j.logging.LogProvider; +import org.neo4j.server.security.auth.exception.FormatException; import static java.nio.file.StandardCopyOption.ATOMIC_MOVE; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; @@ -37,8 +38,6 @@ */ public class FileRoleRepository extends AbstractRoleRepository { - // TODO: Extract shared code with FileUserRepository - private final Path roleFile; private final Log log; @@ -56,57 +55,30 @@ public void start() throws Throwable { if ( Files.exists( roleFile ) ) { - loadRolesFromFile(); - } - } - - @Override - protected void saveRoles() throws IOException - { - saveRolesToFile(); - } + List loadedRoles; + try + { + loadedRoles = serialization.loadRecordsFromFile( roleFile ); + } + catch ( FormatException e ) + { + log.error( "Failed to read role file \"%s\" (%s)", roleFile.toAbsolutePath(), e.getMessage() ); + throw new IllegalStateException( "Failed to read role file: " + roleFile ); + } - private void saveRolesToFile() throws IOException - { - Path directory = roleFile.getParent(); - if ( !Files.exists( directory ) ) - { - Files.createDirectories( directory ); - } + roles = loadedRoles; + for ( RoleRecord role : roles ) + { + rolesByName.put( role.name(), role ); - Path tempFile = Files.createTempFile( directory, roleFile.getFileName().toString() + "-", ".tmp" ); - try - { - Files.write( tempFile, serialization.serialize( roles ) ); - Files.move( tempFile, roleFile, ATOMIC_MOVE, REPLACE_EXISTING ); - } - catch ( Throwable e ) - { - Files.delete( tempFile ); - throw e; + populateUserMap( role ); + } } } - private void loadRolesFromFile() throws IOException + @Override + protected void saveRoles() throws IOException { - byte[] fileBytes = Files.readAllBytes( roleFile ); - List loadedRoles; - try - { - loadedRoles = serialization.deserializeRoles( fileBytes ); - } - catch ( RoleSerialization.FormatException e ) - { - log.error( "Failed to read role file \"%s\" (%s)", roleFile.toAbsolutePath(), e.getMessage() ); - throw new IllegalStateException( "Failed to read role file: " + roleFile ); - } - - roles = loadedRoles; - for ( RoleRecord role : roles ) - { - rolesByName.put( role.name(), role ); - - populateUserMap( role ); - } + serialization.saveRecordsToFile( roleFile, roles ); } } diff --git a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/RoleSerialization.java b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/RoleSerialization.java index aa1f2a879050f..aa3ecfbf6e896 100644 --- a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/RoleSerialization.java +++ b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/RoleSerialization.java @@ -19,63 +19,30 @@ */ package org.neo4j.server.security.enterprise.auth; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; import java.util.SortedSet; import java.util.TreeSet; -import org.neo4j.string.UTF8; +import org.neo4j.server.security.auth.FileRepositorySerializer; +import org.neo4j.server.security.auth.exception.FormatException; import static java.lang.String.format; /** * Serializes role authorization and authentication data to a format similar to unix passwd files. */ -public class RoleSerialization +public class RoleSerialization extends FileRepositorySerializer { - public class FormatException extends Exception - { - FormatException( String message ) - { - super( message ); - } - } - private static final String roleSeparator = ":"; private static final String userSeparator = ","; - public byte[] serialize( Collection roles ) - { - StringBuilder sb = new StringBuilder(); - for ( RoleRecord role : roles ) - { - sb.append( serialize( role ) ).append( "\n" ); - } - return UTF8.encode( sb.toString() ); - } - - public List deserializeRoles( byte[] bytes ) throws FormatException - { - List out = new ArrayList<>(); - int lineNumber = 1; - for ( String line : UTF8.decode( bytes ).split( "\n" ) ) - { - if ( line.trim().length() > 0 ) - { - out.add( deserializeRole( line, lineNumber ) ); - } - lineNumber++; - } - return out; - } - - private String serialize( RoleRecord role ) + @Override + protected String serialize( RoleRecord role ) { - return join( roleSeparator, role.name(), serialize( role.users() ) ); + return String.join( roleSeparator, role.name(), String.join( userSeparator, role.users() ) ); } - private RoleRecord deserializeRole( String line, int lineNumber ) throws FormatException + @Override + protected RoleRecord deserializeRecord( String line, int lineNumber ) throws FormatException { String[] parts = line.split( roleSeparator, -1 ); if ( parts.length != 2 ) @@ -88,11 +55,6 @@ private RoleRecord deserializeRole( String line, int lineNumber ) throws FormatE .build(); } - private String serialize( SortedSet users ) - { - return joinCollection( userSeparator, users ); - } - private SortedSet deserializeUsers( String part ) throws FormatException { String[] splits = part.split( userSeparator, -1 ); @@ -109,28 +71,4 @@ private SortedSet deserializeUsers( String part ) throws FormatException return users; } - - private String joinCollection( String separator, Collection segments ) - { - StringBuilder sb = new StringBuilder(); - int i = 0; - for ( String segment : segments ) - { - if ( i > 0 ) { sb.append( separator ); } - sb.append( segment == null ? "" : segment ); - i++; - } - return sb.toString(); - } - - private String join( String separator, String... segments ) - { - StringBuilder sb = new StringBuilder(); - for ( int i = 0; i < segments.length; i++ ) - { - if ( i > 0 ) { sb.append( separator ); } - sb.append( segments[i] == null ? "" : segments[i] ); - } - return sb.toString(); - } } diff --git a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/RoleSerializationTest.java b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/RoleSerializationTest.java index 320f8840e696f..3dde71c00c554 100644 --- a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/RoleSerializationTest.java +++ b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/RoleSerializationTest.java @@ -64,7 +64,7 @@ public void shouldSerializeAndDeserialize() throws Exception byte[] serialized = serialization.serialize( roles ); // Then - assertThat( serialization.deserializeRoles( serialized ), equalTo( roles ) ); + assertThat( serialization.deserializeRecords( serialized ), equalTo( roles ) ); } /** @@ -78,7 +78,7 @@ public void shouldReadV1SerializationFormat() throws Exception RoleSerialization serialization = new RoleSerialization(); // When - List deserialized = serialization.deserializeRoles( UTF8.encode( + List deserialized = serialization.deserializeRecords( UTF8.encode( ("admin:Bob,Steve\n" + "publisher:Kelly,Marie\n") ) );