Skip to content

Commit 093fcad

Browse files
committed
2 parents d6e5354 + dd7e584 commit 093fcad

File tree

8 files changed

+200
-25
lines changed

8 files changed

+200
-25
lines changed

pom.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,9 @@
131131
<configuration>
132132
<excludes>
133133
<exclude>**/*Application.*</exclude>
134+
<exclude>**/*Builder*</exclude>
135+
<exclude>**/*Properties.*</exclude>
134136
<exclude>**/*Configuration.*</exclude>
135-
<exclude>**/*GrpcRole.*</exclude>
136137
<exclude>**/proto/**/*</exclude>
137138
</excludes>
138139
</configuration>

src/main/java/io/github/majusko/grpc/jwt/exception/UnauthenticatedException.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@ public class UnauthenticatedException extends RuntimeException {
44
public UnauthenticatedException(String message) {
55
super(message);
66
}
7+
public UnauthenticatedException(String message, Throwable cause) {
8+
super(message, cause);
9+
}
710
}

src/main/java/io/github/majusko/grpc/jwt/interceptor/AuthServerInterceptor.java

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
import io.github.majusko.grpc.jwt.exception.UnauthenticatedException;
77
import io.github.majusko.grpc.jwt.service.JwtService;
88
import io.grpc.*;
9-
import io.jsonwebtoken.Claims;
10-
import io.jsonwebtoken.Jwts;
9+
import io.jsonwebtoken.*;
10+
import io.jsonwebtoken.security.SignatureException;
1111
import org.lognet.springboot.grpc.GRpcGlobalInterceptor;
1212
import org.springframework.core.env.Environment;
1313

@@ -50,10 +50,6 @@ public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
5050
call.close(Status.UNAUTHENTICATED.withDescription(e.getMessage()).withCause(e.getCause()), metadata);
5151
//noinspection unchecked
5252
return NOOP_LISTENER;
53-
} catch (Exception e) {
54-
call.close(Status.INTERNAL.withDescription(e.getMessage()).withCause(e.getCause()), metadata);
55-
//noinspection unchecked
56-
return NOOP_LISTENER;
5753
}
5854
}
5955

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

79-
private void handlingException(Status status, Exception e) {
80-
call.close(status.withDescription(e.getMessage()).withCause(e.getCause()), metadata);
81-
}
82-
8375
@Override
8476
public void onMessage(ReqT request) {
8577
try {
@@ -90,12 +82,10 @@ public void onMessage(ReqT request) {
9082

9183
delegate = customDelegate;
9284
}
93-
} catch (UnauthenticatedException e) {
94-
handlingException(Status.UNAUTHENTICATED, e);
9585
} catch (AuthException e) {
96-
handlingException(Status.PERMISSION_DENIED, e);
97-
} catch (Exception e) {
98-
handlingException(Status.INTERNAL, e);
86+
call.close(Status.PERMISSION_DENIED
87+
.withDescription(e.getMessage())
88+
.withCause(e.getCause()), metadata);
9989
}
10090
super.onMessage(request);
10191
}
@@ -162,11 +152,7 @@ private void validateRoles(Set<String> requiredRoles, Set<String> userRoles) {
162152
throw new AuthException("Endpoint does not have specified roles.");
163153
}
164154

165-
if (userRoles == null) {
166-
throw new AuthException("User doesn't have any roles.");
167-
}
168-
169-
requiredRoles.retainAll(userRoles);
155+
requiredRoles.retainAll(Objects.requireNonNull(userRoles));
170156

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

189175
return new AuthContextData(token, jwtBody.getSubject(), Sets.newHashSet(roles), jwtBody);
190-
} catch (Exception e) {
191-
throw new UnauthenticatedException(e.getMessage());
176+
} catch (JwtException | IllegalArgumentException e) {
177+
throw new UnauthenticatedException(e.getMessage(), e);
192178
}
193179
}
194180
}

src/main/java/io/github/majusko/grpc/jwt/interceptor/GrpcHeader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import static io.grpc.Metadata.ASCII_STRING_MARSHALLER;
66

77
public class GrpcHeader {
8-
8+
private GrpcHeader(){}
99
private final static String AUTHORIZATION_KEY = "Authorization";
1010
public static Metadata.Key<String> AUTHORIZATION =
1111
Metadata.Key.of(AUTHORIZATION_KEY, ASCII_STRING_MARSHALLER);

src/main/java/io/github/majusko/grpc/jwt/interceptor/GrpcJwtContext.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
public class GrpcJwtContext {
66

7+
private GrpcJwtContext(){}
8+
79
private static final String CONTEXT_DATA = "context_data";
810

911
public static io.grpc.Context.Key<AuthContextData> CONTEXT_DATA_KEY = io.grpc.Context.key(CONTEXT_DATA);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.github.majusko.grpc.jwt.service;
22

33
public class GrpcRole {
4+
private GrpcRole(){}
45
public static final String INTERNAL = "internal_role";
56
}

src/test/java/io/github/majusko/grpc/jwt/GrpcJwtSpringBootStarterApplicationTest.java

Lines changed: 180 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.github.majusko.grpc.jwt;
22

3+
import com.google.common.collect.Sets;
34
import com.google.protobuf.Empty;
45
import io.github.majusko.grpc.jwt.annotation.Allow;
56
import io.github.majusko.grpc.jwt.annotation.Exposed;
@@ -22,16 +23,21 @@
2223
import org.lognet.springboot.grpc.GRpcService;
2324
import org.springframework.beans.factory.annotation.Autowired;
2425
import org.springframework.boot.test.context.SpringBootTest;
26+
import org.springframework.core.env.Environment;
2527
import org.springframework.test.context.ActiveProfiles;
2628
import org.springframework.test.context.junit4.SpringRunner;
2729

2830
import java.io.IOException;
31+
import java.lang.reflect.Field;
2932

3033
@RunWith(SpringRunner.class)
3134
@SpringBootTest
3235
@ActiveProfiles("test")
3336
public class GrpcJwtSpringBootStarterApplicationTest {
3437

38+
@Autowired
39+
private Environment environment;
40+
3541
@Autowired
3642
private JwtService jwtService;
3743

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

219+
@Test
220+
public void testSuccessExposeToTestEnvAnnotation() throws IOException {
221+
final ManagedChannel channel = initTestServer(new ExampleService());
222+
final Channel interceptedChannel = ClientInterceptors.intercept(channel, authClientInterceptor);
223+
final ExampleServiceGrpc.ExampleServiceBlockingStub stub = ExampleServiceGrpc.newBlockingStub(interceptedChannel);
224+
225+
final Empty response = stub.listExample(Example.GetExampleRequest.newBuilder().build());
226+
227+
Assert.assertNotNull(response);
228+
}
229+
230+
@Test
231+
public void testNonExistingFieldInPayload() throws IOException {
232+
final ManagedChannel channel = initTestServer(new ExampleService());
233+
final Channel interceptedChannel = ClientInterceptors.intercept(channel, authClientInterceptor);
234+
final ExampleServiceGrpc.ExampleServiceBlockingStub stub = ExampleServiceGrpc.newBlockingStub(interceptedChannel);
235+
236+
Status status = Status.OK;
237+
238+
try {
239+
final Empty ignored = stub.saveExample(Empty.getDefaultInstance());
240+
} catch (StatusRuntimeException e) {
241+
status = e.getStatus();
242+
}
243+
244+
Assert.assertEquals(Status.PERMISSION_DENIED.getCode(), status.getCode());
245+
}
246+
247+
@Test
248+
public void testDiffUserIdAndNonExistingRole() throws IOException {
249+
final ManagedChannel channel = initTestServer(new ExampleService());
250+
final Channel interceptedChannel = ClientInterceptors.intercept(channel, authClientInterceptor);
251+
final ExampleServiceGrpc.ExampleServiceBlockingStub stub = ExampleServiceGrpc.newBlockingStub(interceptedChannel);
252+
253+
Status status = Status.OK;
254+
255+
try {
256+
final Empty ignored = stub.deleteExample(Example.GetExampleRequest.getDefaultInstance());
257+
} catch (StatusRuntimeException e) {
258+
status = e.getStatus();
259+
}
260+
261+
Assert.assertEquals(Status.PERMISSION_DENIED.getCode(), status.getCode());
262+
}
263+
264+
@Test
265+
public void testCustomTokenWithEmptyUserIdAndEmptyRoles() throws IOException {
266+
final String token = jwtService.generate(new JwtData("random-user-id", Sets.newHashSet()));
267+
268+
final ManagedChannel channel = initTestServer(new ExampleService());
269+
final ExampleServiceGrpc.ExampleServiceBlockingStub stub = ExampleServiceGrpc.newBlockingStub(channel);
270+
271+
final Metadata header = new Metadata();
272+
header.put(GrpcHeader.AUTHORIZATION, token);
273+
274+
final ExampleServiceGrpc.ExampleServiceBlockingStub injectedStub = MetadataUtils.attachHeaders(stub, header);
275+
final Example.GetExampleRequest request = Example.GetExampleRequest.newBuilder()
276+
.setUserId("other-user-id").build();
277+
278+
Status status = Status.OK;
279+
280+
try {
281+
final Empty ignore = injectedStub.getExample(request);
282+
} catch (StatusRuntimeException e) {
283+
status = e.getStatus();
284+
}
285+
286+
Assert.assertEquals(Status.PERMISSION_DENIED.getCode(), status.getCode());
287+
}
288+
289+
@Test
290+
public void testEmptyUserIdInToken() throws IOException {
291+
final String token = jwtService.generate(new JwtData("", Sets.newHashSet(ExampleService.ADMIN)));
292+
293+
final ManagedChannel channel = initTestServer(new ExampleService());
294+
final ExampleServiceGrpc.ExampleServiceBlockingStub stub = ExampleServiceGrpc.newBlockingStub(channel);
295+
296+
final Metadata header = new Metadata();
297+
header.put(GrpcHeader.AUTHORIZATION, token);
298+
299+
final ExampleServiceGrpc.ExampleServiceBlockingStub injectedStub = MetadataUtils.attachHeaders(stub, header);
300+
final Example.GetExampleRequest request = Example.GetExampleRequest.newBuilder()
301+
.setUserId("other-user-id").build();
302+
303+
Status status = Status.OK;
304+
305+
try {
306+
final Empty ignore = injectedStub.getExample(request);
307+
} catch (StatusRuntimeException e) {
308+
status = e.getStatus();
309+
}
310+
311+
Assert.assertEquals(Status.PERMISSION_DENIED.getCode(), status.getCode());
312+
}
313+
314+
@Test
315+
public void testExpiredToken() throws IOException, NoSuchFieldException, IllegalAccessException {
316+
317+
final GrpcJwtProperties customProperties = new GrpcJwtProperties();
318+
final Field field = customProperties.getClass().getDeclaredField("expirationSec");
319+
field.setAccessible(true);
320+
field.set(customProperties, -10L);
321+
322+
323+
final JwtService customJwtService = new JwtService(environment, customProperties);
324+
final String token = customJwtService.generate(new JwtData("lala", Sets.newHashSet(ExampleService.ADMIN)));
325+
326+
final ManagedChannel channel = initTestServer(new ExampleService());
327+
final Channel interceptedChannel = ClientInterceptors.intercept(channel, authClientInterceptor);
328+
final ExampleServiceGrpc.ExampleServiceBlockingStub stub = ExampleServiceGrpc.newBlockingStub(interceptedChannel);
329+
330+
final Metadata header = new Metadata();
331+
header.put(GrpcHeader.AUTHORIZATION, token);
332+
333+
final ExampleServiceGrpc.ExampleServiceBlockingStub injectedStub = MetadataUtils.attachHeaders(stub, header);
334+
final Example.GetExampleRequest request = Example.GetExampleRequest.newBuilder()
335+
.setUserId("other-user-id").build();
336+
337+
Status status = Status.OK;
338+
339+
try {
340+
final Empty ignore = injectedStub.getExample(request);
341+
} catch (StatusRuntimeException e) {
342+
status = e.getStatus();
343+
}
344+
345+
Assert.assertEquals(Status.UNAUTHENTICATED.getCode(), status.getCode());
346+
}
347+
348+
@Test
349+
public void testEmptyOwnerFieldInAnnotationSoRolesAreValidated() throws IOException {
350+
final String token = jwtService
351+
.generate(new JwtData("random-user-id", Sets.newHashSet(ExampleService.ADMIN)));
352+
353+
final ManagedChannel channel = initTestServer(new ExampleService());
354+
final ExampleServiceGrpc.ExampleServiceBlockingStub stub = ExampleServiceGrpc.newBlockingStub(channel);
355+
356+
final Metadata header = new Metadata();
357+
header.put(GrpcHeader.AUTHORIZATION, token);
358+
359+
final ExampleServiceGrpc.ExampleServiceBlockingStub injectedStub = MetadataUtils.attachHeaders(stub, header);
360+
final Example.GetExampleRequest request = Example.GetExampleRequest.newBuilder()
361+
.setUserId("other-user-id").build();
362+
363+
final Empty response = injectedStub.someAction(request);
364+
365+
Assert.assertNotNull(response);
366+
}
367+
213368
private ManagedChannel initTestServer(BindableService service) throws IOException {
214369

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

233-
private static final String ADMIN = "admin";
388+
public static final String ADMIN = "admin";
234389

235390
@Override
236391
@Allow(ownerField = "userId", roles = {GrpcRole.INTERNAL, ADMIN})
@@ -257,4 +412,28 @@ public void listExample(Example.GetExampleRequest request, StreamObserver<Empty>
257412
response.onNext(Empty.getDefaultInstance());
258413
response.onCompleted();
259414
}
415+
416+
@Override
417+
@Allow(ownerField = "nonExistingField")
418+
public void saveExample(Empty request, StreamObserver<Empty> response) {
419+
420+
response.onNext(Empty.getDefaultInstance());
421+
response.onCompleted();
422+
}
423+
424+
@Override
425+
@Allow(ownerField = "userId")
426+
public void deleteExample(Example.GetExampleRequest request, StreamObserver<Empty> response) {
427+
428+
response.onNext(Empty.getDefaultInstance());
429+
response.onCompleted();
430+
}
431+
432+
@Override
433+
@Allow(ownerField = "", roles = {ADMIN})
434+
public void someAction(Example.GetExampleRequest request, StreamObserver<Empty> response) {
435+
436+
response.onNext(Empty.getDefaultInstance());
437+
response.onCompleted();
438+
}
260439
}

src/test/proto/Example.proto

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import "google/protobuf/empty.proto";
66
service ExampleService {
77
rpc GetExample (GetExampleRequest) returns (google.protobuf.Empty);
88
rpc ListExample (GetExampleRequest) returns (google.protobuf.Empty);
9+
rpc SaveExample (google.protobuf.Empty) returns (google.protobuf.Empty);
10+
rpc DeleteExample (GetExampleRequest) returns (google.protobuf.Empty);
11+
rpc SomeAction (GetExampleRequest) returns (google.protobuf.Empty);
912
}
1013

1114
message GetExampleRequest {

0 commit comments

Comments
 (0)