diff --git a/community/kernel/src/main/java/org/neo4j/kernel/api/KernelTransaction.java b/community/kernel/src/main/java/org/neo4j/kernel/api/KernelTransaction.java index 8c1e7eb3d543..e82b4c29848b 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/api/KernelTransaction.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/api/KernelTransaction.java @@ -23,6 +23,7 @@ import org.neo4j.internal.kernel.api.PropertyCursor; import org.neo4j.internal.kernel.api.RelationshipScanCursor; import org.neo4j.internal.kernel.api.Transaction; +import org.neo4j.internal.kernel.api.security.AuthSubject; import org.neo4j.internal.kernel.api.security.LoginContext; import org.neo4j.internal.kernel.api.security.SecurityContext; import org.neo4j.kernel.impl.api.ClockContext; @@ -102,6 +103,11 @@ interface CloseListener */ SecurityContext securityContext(); + /** + * @return the subject executing this transaction. + */ + AuthSubject subject(); + /** * @return The timestamp of the last transaction that was committed to the store when this transaction started. */ diff --git a/community/kernel/src/main/java/org/neo4j/kernel/api/KernelTransactionHandle.java b/community/kernel/src/main/java/org/neo4j/kernel/api/KernelTransactionHandle.java index 6041b106102f..8a61a4797654 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/api/KernelTransactionHandle.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/api/KernelTransactionHandle.java @@ -23,7 +23,7 @@ import java.util.Optional; import java.util.stream.Stream; -import org.neo4j.internal.kernel.api.security.SecurityContext; +import org.neo4j.internal.kernel.api.security.AuthSubject; import org.neo4j.kernel.api.exceptions.Status; import org.neo4j.kernel.api.query.ExecutingQuery; import org.neo4j.kernel.impl.api.TransactionExecutionStatistic; @@ -84,7 +84,7 @@ public interface KernelTransactionHandle * * @return underlying transaction security context */ - SecurityContext securityContext(); + AuthSubject subject(); /** * Metadata of underlying transaction that transaction has when handle was created. diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/KernelTransactionImplementation.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/KernelTransactionImplementation.java index dbf2c7d4e634..efc8b604c5e5 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/KernelTransactionImplementation.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/KernelTransactionImplementation.java @@ -54,6 +54,7 @@ import org.neo4j.internal.kernel.api.exceptions.InvalidTransactionTypeKernelException; import org.neo4j.internal.kernel.api.exceptions.schema.ConstraintValidationException; import org.neo4j.internal.kernel.api.security.AccessMode; +import org.neo4j.internal.kernel.api.security.AuthSubject; import org.neo4j.internal.kernel.api.security.SecurityContext; import org.neo4j.io.pagecache.tracing.cursor.PageCursorTracer; import org.neo4j.io.pagecache.tracing.cursor.PageCursorTracerSupplier; @@ -366,9 +367,18 @@ public boolean isOpen() @Override public SecurityContext securityContext() { + if ( securityContext == null ) + { + throw new NotInTransactionException(); + } return securityContext; } + public AuthSubject subject() + { + return securityContext == null ? AuthSubject.ANONYMOUS : securityContext.subject(); + } + public void setMetaData( Map data ) { this.userMetaData = data; @@ -828,6 +838,7 @@ public org.neo4j.internal.kernel.api.Locks locks() public StatementLocks statementLocks() { + assertOpen(); return statementLocks; } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/KernelTransactionImplementationHandle.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/KernelTransactionImplementationHandle.java index 49cc8df4fda8..f762847be6ce 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/KernelTransactionImplementationHandle.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/KernelTransactionImplementationHandle.java @@ -23,6 +23,7 @@ import java.util.Optional; import java.util.stream.Stream; +import org.neo4j.internal.kernel.api.security.AuthSubject; import org.neo4j.internal.kernel.api.security.SecurityContext; import org.neo4j.kernel.api.KernelTransaction; import org.neo4j.kernel.api.KernelTransactionHandle; @@ -48,7 +49,7 @@ class KernelTransactionImplementationHandle implements KernelTransactionHandle private final long timeoutMillis; private final KernelTransactionImplementation tx; private final SystemNanoClock clock; - private final SecurityContext securityContext; + private final AuthSubject subject; private final Optional terminationReason; private final ExecutingQueryList executingQueries; private final Map metaData; @@ -61,7 +62,7 @@ class KernelTransactionImplementationHandle implements KernelTransactionHandle this.lastTransactionTimestampWhenStarted = tx.lastTransactionTimestampWhenStarted(); this.startTime = tx.startTime(); this.timeoutMillis = tx.timeout(); - this.securityContext = tx.securityContext(); + this.subject = tx.subject(); this.terminationReason = tx.getReasonIfTerminated(); this.executingQueries = tx.executingQueries(); this.metaData = tx.getMetaData(); @@ -107,9 +108,9 @@ public boolean markForTermination( Status reason ) } @Override - public SecurityContext securityContext() + public AuthSubject subject() { - return securityContext; + return subject; } @Override diff --git a/community/kernel/src/test/java/org/neo4j/kernel/builtinprocs/StubKernelTransaction.java b/community/kernel/src/test/java/org/neo4j/kernel/builtinprocs/StubKernelTransaction.java index 05ccf17bf135..100ae6b9c132 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/builtinprocs/StubKernelTransaction.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/builtinprocs/StubKernelTransaction.java @@ -37,6 +37,7 @@ import org.neo4j.internal.kernel.api.TokenRead; import org.neo4j.internal.kernel.api.TokenWrite; import org.neo4j.internal.kernel.api.Write; +import org.neo4j.internal.kernel.api.security.AuthSubject; import org.neo4j.internal.kernel.api.security.SecurityContext; import org.neo4j.kernel.api.KernelTransaction; import org.neo4j.kernel.api.Statement; @@ -176,6 +177,12 @@ public SecurityContext securityContext() throw new UnsupportedOperationException( "not implemented" ); } + @Override + public AuthSubject subject() + { + throw new UnsupportedOperationException( "not implemented" ); + } + @Override public Optional getReasonIfTerminated() { diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/KernelTransactionAssertOpenTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/KernelTransactionAssertOpenTest.java new file mode 100644 index 000000000000..9088c2d2b734 --- /dev/null +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/KernelTransactionAssertOpenTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2002-2018 "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.kernel.impl.api; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; + +import org.neo4j.graphdb.NotInTransactionException; +import org.neo4j.graphdb.TransactionTerminatedException; +import org.neo4j.internal.kernel.api.Read; +import org.neo4j.internal.kernel.api.SchemaRead; +import org.neo4j.internal.kernel.api.Write; +import org.neo4j.internal.kernel.api.exceptions.KernelException; +import org.neo4j.kernel.api.KernelTransaction; +import org.neo4j.kernel.api.exceptions.Status; +import org.neo4j.kernel.api.security.AnonymousContext; +import org.neo4j.values.storable.Values; + +@RunWith( Parameterized.class ) +public class KernelTransactionAssertOpenTest extends KernelTransactionTestBase +{ + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Parameterized.Parameter( 0 ) + public String name; + + @Parameterized.Parameter( 1 ) + public Operation transactionOperation; + + @Parameterized.Parameters( name = "{0}" ) + public static Collection parameters() + { + // Some samples of operations that should react to transaction killing. Not exhaustive. + return Arrays.asList( + operation( "nodeExists", ( read, write, schema ) -> read.nodeExists( 0 ) ), + operation( "singleRelationship", ( read, write, schema ) -> read.singleRelationship( 0, null ) ), + operation( "nodeCreate", ( read, write, schema ) -> write.nodeCreate() ), + operation( "relationshipSetProperty", ( read, write, schema ) -> write.relationshipSetProperty( 0, 2, Values.longValue( 42 ) ) ), + operation( "indexesGetAll", ( read, write, schema ) -> schema.indexesGetAll() ) ); + } + + private static Object[] operation( String name, Operation op ) + { + return new Object[]{name, op}; + } + + @Test( expected = TransactionTerminatedException.class ) + public void shouldThrowTerminateExceptionWhenTransactionTerminated() throws KernelException + { + KernelTransaction transaction = newTransaction( AnonymousContext.write() ); + + transaction.success(); + transaction.markForTermination( Status.General.UnknownError ); + + transactionOperation.operate( transaction.dataRead(), transaction.dataWrite(), transaction.schemaRead() ); + } + + @Test( expected = NotInTransactionException.class ) + public void shouldThrowNotInTransactionWhenTransactionClosedAndAccessingOperations() throws KernelException + { + KernelTransaction transaction = newTransaction( AnonymousContext.write() ); + + transaction.success(); + transaction.close(); + + transactionOperation.operate( transaction.dataRead(), transaction.dataWrite(), transaction.schemaRead() ); + } + + @Test( expected = NotInTransactionException.class ) + public void shouldThrowNotInTransactionWhenTransactionClosedAndAttemptingOperations() throws KernelException + { + KernelTransaction transaction = newTransaction( AnonymousContext.write() ); + + Read read = transaction.dataRead(); + Write write = transaction.dataWrite(); + SchemaRead schemaRead = transaction.schemaRead(); + + transaction.success(); + transaction.close(); + + transactionOperation.operate( read, write, schemaRead ); + } + + interface Operation + { + void operate( Read read, Write write, SchemaRead schemaRead ) throws KernelException; + } +} diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/TestKernelTransactionHandle.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/TestKernelTransactionHandle.java index cd4368b5b3d8..9c44594facdc 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/TestKernelTransactionHandle.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/TestKernelTransactionHandle.java @@ -25,7 +25,7 @@ import java.util.Optional; import java.util.stream.Stream; -import org.neo4j.internal.kernel.api.security.SecurityContext; +import org.neo4j.internal.kernel.api.security.AuthSubject; import org.neo4j.kernel.api.KernelTransaction; import org.neo4j.kernel.api.KernelTransactionHandle; import org.neo4j.kernel.api.exceptions.Status; @@ -83,9 +83,9 @@ public boolean markForTermination( Status reason ) } @Override - public SecurityContext securityContext() + public AuthSubject subject() { - return tx.securityContext(); + return tx.subject(); } @Override diff --git a/community/neo4j/src/test/java/org/neo4j/locking/QueryExecutionLocksIT.java b/community/neo4j/src/test/java/org/neo4j/locking/QueryExecutionLocksIT.java index 55f834fedf11..7a10871a4aeb 100644 --- a/community/neo4j/src/test/java/org/neo4j/locking/QueryExecutionLocksIT.java +++ b/community/neo4j/src/test/java/org/neo4j/locking/QueryExecutionLocksIT.java @@ -53,6 +53,7 @@ import org.neo4j.internal.kernel.api.Write; import org.neo4j.internal.kernel.api.exceptions.InvalidTransactionTypeKernelException; import org.neo4j.internal.kernel.api.exceptions.TransactionFailureException; +import org.neo4j.internal.kernel.api.security.AuthSubject; import org.neo4j.internal.kernel.api.security.LoginContext; import org.neo4j.internal.kernel.api.security.SecurityContext; import org.neo4j.kernel.GraphDatabaseQueryService; @@ -695,6 +696,12 @@ public SecurityContext securityContext() return internal.securityContext(); } + @Override + public AuthSubject subject() + { + return internal.subject(); + } + @Override public Optional getReasonIfTerminated() { diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/enterprise/builtinprocs/EnterpriseBuiltInDbmsProcedures.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/enterprise/builtinprocs/EnterpriseBuiltInDbmsProcedures.java index 52be2be64992..00fdaf4ae373 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/enterprise/builtinprocs/EnterpriseBuiltInDbmsProcedures.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/enterprise/builtinprocs/EnterpriseBuiltInDbmsProcedures.java @@ -310,7 +310,7 @@ public Stream listTransactions() throws InvalidArgument try { Set handles = getKernelTransactions().activeTransactions().stream() - .filter( transaction -> isAdminOrSelf( transaction.securityContext().subject().username() ) ) + .filter( transaction -> isAdminOrSelf( transaction.subject().username() ) ) .collect( toSet() ); Map> handleQuerySnapshotsMap = handles.stream() @@ -475,7 +475,7 @@ public static Stream terminateTransactionsForValid { long terminatedCount = getActiveTransactions( dependencyResolver ) .stream() - .filter( tx -> tx.securityContext().subject().hasUsername( username ) && + .filter( tx -> tx.subject().hasUsername( username ) && !tx.isUnderlyingTransaction( currentTx ) ) .map( tx -> tx.markForTermination( Status.Transaction.Terminated ) ) .filter( marked -> marked ) diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/enterprise/builtinprocs/TransactionStatusResult.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/enterprise/builtinprocs/TransactionStatusResult.java index b15f6288c851..56d47de2df8f 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/enterprise/builtinprocs/TransactionStatusResult.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/enterprise/builtinprocs/TransactionStatusResult.java @@ -71,7 +71,7 @@ public TransactionStatusResult( KernelTransactionHandle transaction, Map> handleSnapshotsMap, ZoneId zoneId ) throws InvalidArgumentsException { this.transactionId = transaction.getUserTransactionName(); - this.username = transaction.securityContext().subject().username(); + this.username = transaction.subject().username(); this.startTime = ProceduresTimeFormatHelper.formatTime( transaction.startTime(), zoneId ); Optional terminationReason = transaction.terminationReason(); this.activeLockCount = transaction.activeLocks().count(); diff --git a/enterprise/kernel/src/test/java/org/neo4j/kernel/enterprise/builtinprocs/StubKernelTransaction.java b/enterprise/kernel/src/test/java/org/neo4j/kernel/enterprise/builtinprocs/StubKernelTransaction.java index 1986536437b5..3c14fbfb1f2a 100644 --- a/enterprise/kernel/src/test/java/org/neo4j/kernel/enterprise/builtinprocs/StubKernelTransaction.java +++ b/enterprise/kernel/src/test/java/org/neo4j/kernel/enterprise/builtinprocs/StubKernelTransaction.java @@ -39,6 +39,7 @@ import org.neo4j.internal.kernel.api.TokenRead; import org.neo4j.internal.kernel.api.TokenWrite; import org.neo4j.internal.kernel.api.Write; +import org.neo4j.internal.kernel.api.security.AuthSubject; import org.neo4j.internal.kernel.api.security.SecurityContext; import org.neo4j.kernel.api.KernelTransaction; import org.neo4j.kernel.api.Statement; @@ -176,6 +177,14 @@ public SecurityContext securityContext() return securityContext; } + @Override + public AuthSubject subject() + { + AuthSubject subject = mock( AuthSubject.class ); + when( subject.username() ).thenReturn( "testUser" ); + return subject; + } + @Override public Optional getReasonIfTerminated() { diff --git a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/AuthProceduresBase.java b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/AuthProceduresBase.java index a8d73eb60a88..bb23ed6fa05e 100644 --- a/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/AuthProceduresBase.java +++ b/enterprise/security/src/main/java/org/neo4j/server/security/enterprise/auth/AuthProceduresBase.java @@ -76,7 +76,7 @@ protected void terminateTransactionsForValidUser( String username ) getActiveTransactions() .stream() .filter( tx -> - tx.securityContext().subject().hasUsername( username ) && + tx.subject().hasUsername( username ) && !tx.isUnderlyingTransaction( currentTx ) ).forEach( tx -> tx.markForTermination( Status.Transaction.Terminated ) ); } diff --git a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/ProcedureInteractionTestBase.java b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/ProcedureInteractionTestBase.java index ee29d20d3ecf..c492e416d15e 100644 --- a/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/ProcedureInteractionTestBase.java +++ b/enterprise/security/src/test/java/org/neo4j/server/security/enterprise/auth/ProcedureInteractionTestBase.java @@ -583,7 +583,7 @@ private Map countTransactionsByUsername() neo.getLocalGraph().getDependencyResolver() ).stream() .filter( tx -> !tx.terminationReason().isPresent() ) - .map( tx -> tx.securityContext().subject().username() ) + .map( tx -> tx.subject().username() ) ).collect( Collectors.toMap( r -> r.username, r -> r.activeTransactions ) ); }