Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
majusko committed Nov 11, 2019
2 parents d6e5354 + dd7e584 commit 093fcad
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 25 deletions.
3 changes: 2 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,9 @@
<configuration>
<excludes>
<exclude>**/*Application.*</exclude>
<exclude>**/*Builder*</exclude>
<exclude>**/*Properties.*</exclude>
<exclude>**/*Configuration.*</exclude>
<exclude>**/*GrpcRole.*</exclude>
<exclude>**/proto/**/*</exclude>
</excludes>
</configuration>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ public class UnauthenticatedException extends RuntimeException {
public UnauthenticatedException(String message) {
super(message);
}
public UnauthenticatedException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import io.github.majusko.grpc.jwt.exception.UnauthenticatedException;
import io.github.majusko.grpc.jwt.service.JwtService;
import io.grpc.*;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.SignatureException;
import org.lognet.springboot.grpc.GRpcGlobalInterceptor;
import org.springframework.core.env.Environment;

Expand Down Expand Up @@ -50,10 +50,6 @@ public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
call.close(Status.UNAUTHENTICATED.withDescription(e.getMessage()).withCause(e.getCause()), metadata);
//noinspection unchecked
return NOOP_LISTENER;
} catch (Exception e) {
call.close(Status.INTERNAL.withDescription(e.getMessage()).withCause(e.getCause()), metadata);
//noinspection unchecked
return NOOP_LISTENER;
}
}

Expand All @@ -76,10 +72,6 @@ protected ServerCall.Listener<ReqT> delegate() {
return delegate;
}

private void handlingException(Status status, Exception e) {
call.close(status.withDescription(e.getMessage()).withCause(e.getCause()), metadata);
}

@Override
public void onMessage(ReqT request) {
try {
Expand All @@ -90,12 +82,10 @@ public void onMessage(ReqT request) {

delegate = customDelegate;
}
} catch (UnauthenticatedException e) {
handlingException(Status.UNAUTHENTICATED, e);
} catch (AuthException e) {
handlingException(Status.PERMISSION_DENIED, e);
} catch (Exception e) {
handlingException(Status.INTERNAL, e);
call.close(Status.PERMISSION_DENIED
.withDescription(e.getMessage())
.withCause(e.getCause()), metadata);
}
super.onMessage(request);
}
Expand Down Expand Up @@ -162,11 +152,7 @@ private void validateRoles(Set<String> requiredRoles, Set<String> userRoles) {
throw new AuthException("Endpoint does not have specified roles.");
}

if (userRoles == null) {
throw new AuthException("User doesn't have any roles.");
}

requiredRoles.retainAll(userRoles);
requiredRoles.retainAll(Objects.requireNonNull(userRoles));

if (requiredRoles.isEmpty()) {
throw new AuthException("Missing required permission roles.");
Expand All @@ -187,8 +173,8 @@ private AuthContextData parseAuthContextData(Metadata metadata) {
final List<String> roles = (List<String>) jwtBody.get(JwtService.JWT_ROLES, List.class);

return new AuthContextData(token, jwtBody.getSubject(), Sets.newHashSet(roles), jwtBody);
} catch (Exception e) {
throw new UnauthenticatedException(e.getMessage());
} catch (JwtException | IllegalArgumentException e) {
throw new UnauthenticatedException(e.getMessage(), e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import static io.grpc.Metadata.ASCII_STRING_MARSHALLER;

public class GrpcHeader {

private GrpcHeader(){}
private final static String AUTHORIZATION_KEY = "Authorization";
public static Metadata.Key<String> AUTHORIZATION =
Metadata.Key.of(AUTHORIZATION_KEY, ASCII_STRING_MARSHALLER);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

public class GrpcJwtContext {

private GrpcJwtContext(){}

private static final String CONTEXT_DATA = "context_data";

public static io.grpc.Context.Key<AuthContextData> CONTEXT_DATA_KEY = io.grpc.Context.key(CONTEXT_DATA);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.majusko.grpc.jwt.service;

public class GrpcRole {
private GrpcRole(){}
public static final String INTERNAL = "internal_role";
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.majusko.grpc.jwt;

import com.google.common.collect.Sets;
import com.google.protobuf.Empty;
import io.github.majusko.grpc.jwt.annotation.Allow;
import io.github.majusko.grpc.jwt.annotation.Exposed;
Expand All @@ -22,16 +23,21 @@
import org.lognet.springboot.grpc.GRpcService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;
import java.lang.reflect.Field;

@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("test")
public class GrpcJwtSpringBootStarterApplicationTest {

@Autowired
private Environment environment;

@Autowired
private JwtService jwtService;

Expand Down Expand Up @@ -210,6 +216,155 @@ public void testExposeAnnotationWithMissingInterceptor() throws IOException {
Assert.assertEquals(Status.PERMISSION_DENIED.getCode(), status.getCode());
}

@Test
public void testSuccessExposeToTestEnvAnnotation() throws IOException {
final ManagedChannel channel = initTestServer(new ExampleService());
final Channel interceptedChannel = ClientInterceptors.intercept(channel, authClientInterceptor);
final ExampleServiceGrpc.ExampleServiceBlockingStub stub = ExampleServiceGrpc.newBlockingStub(interceptedChannel);

final Empty response = stub.listExample(Example.GetExampleRequest.newBuilder().build());

Assert.assertNotNull(response);
}

@Test
public void testNonExistingFieldInPayload() throws IOException {
final ManagedChannel channel = initTestServer(new ExampleService());
final Channel interceptedChannel = ClientInterceptors.intercept(channel, authClientInterceptor);
final ExampleServiceGrpc.ExampleServiceBlockingStub stub = ExampleServiceGrpc.newBlockingStub(interceptedChannel);

Status status = Status.OK;

try {
final Empty ignored = stub.saveExample(Empty.getDefaultInstance());
} catch (StatusRuntimeException e) {
status = e.getStatus();
}

Assert.assertEquals(Status.PERMISSION_DENIED.getCode(), status.getCode());
}

@Test
public void testDiffUserIdAndNonExistingRole() throws IOException {
final ManagedChannel channel = initTestServer(new ExampleService());
final Channel interceptedChannel = ClientInterceptors.intercept(channel, authClientInterceptor);
final ExampleServiceGrpc.ExampleServiceBlockingStub stub = ExampleServiceGrpc.newBlockingStub(interceptedChannel);

Status status = Status.OK;

try {
final Empty ignored = stub.deleteExample(Example.GetExampleRequest.getDefaultInstance());
} catch (StatusRuntimeException e) {
status = e.getStatus();
}

Assert.assertEquals(Status.PERMISSION_DENIED.getCode(), status.getCode());
}

@Test
public void testCustomTokenWithEmptyUserIdAndEmptyRoles() throws IOException {
final String token = jwtService.generate(new JwtData("random-user-id", Sets.newHashSet()));

final ManagedChannel channel = initTestServer(new ExampleService());
final ExampleServiceGrpc.ExampleServiceBlockingStub stub = ExampleServiceGrpc.newBlockingStub(channel);

final Metadata header = new Metadata();
header.put(GrpcHeader.AUTHORIZATION, token);

final ExampleServiceGrpc.ExampleServiceBlockingStub injectedStub = MetadataUtils.attachHeaders(stub, header);
final Example.GetExampleRequest request = Example.GetExampleRequest.newBuilder()
.setUserId("other-user-id").build();

Status status = Status.OK;

try {
final Empty ignore = injectedStub.getExample(request);
} catch (StatusRuntimeException e) {
status = e.getStatus();
}

Assert.assertEquals(Status.PERMISSION_DENIED.getCode(), status.getCode());
}

@Test
public void testEmptyUserIdInToken() throws IOException {
final String token = jwtService.generate(new JwtData("", Sets.newHashSet(ExampleService.ADMIN)));

final ManagedChannel channel = initTestServer(new ExampleService());
final ExampleServiceGrpc.ExampleServiceBlockingStub stub = ExampleServiceGrpc.newBlockingStub(channel);

final Metadata header = new Metadata();
header.put(GrpcHeader.AUTHORIZATION, token);

final ExampleServiceGrpc.ExampleServiceBlockingStub injectedStub = MetadataUtils.attachHeaders(stub, header);
final Example.GetExampleRequest request = Example.GetExampleRequest.newBuilder()
.setUserId("other-user-id").build();

Status status = Status.OK;

try {
final Empty ignore = injectedStub.getExample(request);
} catch (StatusRuntimeException e) {
status = e.getStatus();
}

Assert.assertEquals(Status.PERMISSION_DENIED.getCode(), status.getCode());
}

@Test
public void testExpiredToken() throws IOException, NoSuchFieldException, IllegalAccessException {

final GrpcJwtProperties customProperties = new GrpcJwtProperties();
final Field field = customProperties.getClass().getDeclaredField("expirationSec");
field.setAccessible(true);
field.set(customProperties, -10L);


final JwtService customJwtService = new JwtService(environment, customProperties);
final String token = customJwtService.generate(new JwtData("lala", Sets.newHashSet(ExampleService.ADMIN)));

final ManagedChannel channel = initTestServer(new ExampleService());
final Channel interceptedChannel = ClientInterceptors.intercept(channel, authClientInterceptor);
final ExampleServiceGrpc.ExampleServiceBlockingStub stub = ExampleServiceGrpc.newBlockingStub(interceptedChannel);

final Metadata header = new Metadata();
header.put(GrpcHeader.AUTHORIZATION, token);

final ExampleServiceGrpc.ExampleServiceBlockingStub injectedStub = MetadataUtils.attachHeaders(stub, header);
final Example.GetExampleRequest request = Example.GetExampleRequest.newBuilder()
.setUserId("other-user-id").build();

Status status = Status.OK;

try {
final Empty ignore = injectedStub.getExample(request);
} catch (StatusRuntimeException e) {
status = e.getStatus();
}

Assert.assertEquals(Status.UNAUTHENTICATED.getCode(), status.getCode());
}

@Test
public void testEmptyOwnerFieldInAnnotationSoRolesAreValidated() throws IOException {
final String token = jwtService
.generate(new JwtData("random-user-id", Sets.newHashSet(ExampleService.ADMIN)));

final ManagedChannel channel = initTestServer(new ExampleService());
final ExampleServiceGrpc.ExampleServiceBlockingStub stub = ExampleServiceGrpc.newBlockingStub(channel);

final Metadata header = new Metadata();
header.put(GrpcHeader.AUTHORIZATION, token);

final ExampleServiceGrpc.ExampleServiceBlockingStub injectedStub = MetadataUtils.attachHeaders(stub, header);
final Example.GetExampleRequest request = Example.GetExampleRequest.newBuilder()
.setUserId("other-user-id").build();

final Empty response = injectedStub.someAction(request);

Assert.assertNotNull(response);
}

private ManagedChannel initTestServer(BindableService service) throws IOException {

final String serverName = InProcessServerBuilder.generateName();
Expand All @@ -230,7 +385,7 @@ private ManagedChannel initTestServer(BindableService service) throws IOExceptio
@GRpcService
class ExampleService extends ExampleServiceGrpc.ExampleServiceImplBase {

private static final String ADMIN = "admin";
public static final String ADMIN = "admin";

@Override
@Allow(ownerField = "userId", roles = {GrpcRole.INTERNAL, ADMIN})
Expand All @@ -257,4 +412,28 @@ public void listExample(Example.GetExampleRequest request, StreamObserver<Empty>
response.onNext(Empty.getDefaultInstance());
response.onCompleted();
}

@Override
@Allow(ownerField = "nonExistingField")
public void saveExample(Empty request, StreamObserver<Empty> response) {

response.onNext(Empty.getDefaultInstance());
response.onCompleted();
}

@Override
@Allow(ownerField = "userId")
public void deleteExample(Example.GetExampleRequest request, StreamObserver<Empty> response) {

response.onNext(Empty.getDefaultInstance());
response.onCompleted();
}

@Override
@Allow(ownerField = "", roles = {ADMIN})
public void someAction(Example.GetExampleRequest request, StreamObserver<Empty> response) {

response.onNext(Empty.getDefaultInstance());
response.onCompleted();
}
}
3 changes: 3 additions & 0 deletions src/test/proto/Example.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import "google/protobuf/empty.proto";
service ExampleService {
rpc GetExample (GetExampleRequest) returns (google.protobuf.Empty);
rpc ListExample (GetExampleRequest) returns (google.protobuf.Empty);
rpc SaveExample (google.protobuf.Empty) returns (google.protobuf.Empty);
rpc DeleteExample (GetExampleRequest) returns (google.protobuf.Empty);
rpc SomeAction (GetExampleRequest) returns (google.protobuf.Empty);
}

message GetExampleRequest {
Expand Down

0 comments on commit 093fcad

Please sign in to comment.