Skip to content

Commit

Permalink
Add tests for exceptions on attempting to use terminated or closed tx
Browse files Browse the repository at this point in the history
  • Loading branch information
fickludd committed Apr 16, 2018
1 parent 19ebaef commit c4609e7
Show file tree
Hide file tree
Showing 13 changed files with 167 additions and 14 deletions.
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*/
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String, Object> data )
{
this.userMetaData = data;
Expand Down Expand Up @@ -828,6 +838,7 @@ public org.neo4j.internal.kernel.api.Locks locks()

public StatementLocks statementLocks()
{
assertOpen();
return statementLocks;
}

Expand Down
Expand Up @@ -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;
Expand All @@ -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<Status> terminationReason;
private final ExecutingQueryList executingQueries;
private final Map<String,Object> metaData;
Expand All @@ -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();
Expand Down Expand Up @@ -107,9 +108,9 @@ public boolean markForTermination( Status reason )
}

@Override
public SecurityContext securityContext()
public AuthSubject subject()
{
return securityContext;
return subject;
}

@Override
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -176,6 +177,12 @@ public SecurityContext securityContext()
throw new UnsupportedOperationException( "not implemented" );
}

@Override
public AuthSubject subject()
{
throw new UnsupportedOperationException( "not implemented" );
}

@Override
public Optional<Status> getReasonIfTerminated()
{
Expand Down
@@ -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 <http://www.gnu.org/licenses/>.
*/
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<Object[]> 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;
}
}
Expand Up @@ -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;
Expand Down Expand Up @@ -83,9 +83,9 @@ public boolean markForTermination( Status reason )
}

@Override
public SecurityContext securityContext()
public AuthSubject subject()
{
return tx.securityContext();
return tx.subject();
}

@Override
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -695,6 +696,12 @@ public SecurityContext securityContext()
return internal.securityContext();
}

@Override
public AuthSubject subject()
{
return internal.subject();
}

@Override
public Optional<Status> getReasonIfTerminated()
{
Expand Down
Expand Up @@ -310,7 +310,7 @@ public Stream<TransactionStatusResult> listTransactions() throws InvalidArgument
try
{
Set<KernelTransactionHandle> handles = getKernelTransactions().activeTransactions().stream()
.filter( transaction -> isAdminOrSelf( transaction.securityContext().subject().username() ) )
.filter( transaction -> isAdminOrSelf( transaction.subject().username() ) )
.collect( toSet() );

Map<KernelTransactionHandle,List<QuerySnapshot>> handleQuerySnapshotsMap = handles.stream()
Expand Down Expand Up @@ -475,7 +475,7 @@ public static Stream<TransactionTerminationResult> 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 )
Expand Down
Expand Up @@ -71,7 +71,7 @@ public TransactionStatusResult( KernelTransactionHandle transaction,
Map<KernelTransactionHandle,List<QuerySnapshot>> 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<Status> terminationReason = transaction.terminationReason();
this.activeLockCount = transaction.activeLocks().count();
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Status> getReasonIfTerminated()
{
Expand Down
Expand Up @@ -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 ) );
}
Expand Down
Expand Up @@ -583,7 +583,7 @@ private Map<String,Long> 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 ) );
}

Expand Down

0 comments on commit c4609e7

Please sign in to comment.