Skip to content

Commit 2f5d1e8

Browse files
committed
Moved annotation @Secured to service level, added new attribute deferSecured: true (default), enhanced SMI -> now it looks after deferSecured flag
1 parent 2de0cad commit 2f5d1e8

File tree

21 files changed

+203
-154
lines changed

21 files changed

+203
-154
lines changed

services-api/src/main/java/io/scalecube/services/Reflect.java

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import io.scalecube.services.auth.AllowedRole;
1616
import io.scalecube.services.auth.AllowedRoles;
1717
import io.scalecube.services.auth.Secured;
18-
import io.scalecube.services.methods.MethodInfo;
1918
import io.scalecube.services.methods.ServiceRoleDefinition;
2019
import java.lang.reflect.Method;
2120
import java.lang.reflect.ParameterizedType;
@@ -155,28 +154,6 @@ public static Type parameterizedType(Object object) {
155154
return Object.class;
156155
}
157156

158-
/**
159-
* Parse given service interface and method, and return {@link MethodInfo} as result.
160-
*
161-
* @param serviceInterface serviceInterface
162-
* @return {@link MethodInfo} instance
163-
*/
164-
public static MethodInfo methodInfo(Class<?> serviceInterface, Method method) {
165-
return new MethodInfo(
166-
serviceName(serviceInterface),
167-
methodName(method),
168-
parameterizedReturnType(method),
169-
isReturnTypeServiceMessage(method),
170-
communicationMode(method),
171-
method.getParameterCount(),
172-
requestType(method),
173-
isRequestTypeServiceMessage(method),
174-
isSecured(method),
175-
Schedulers.immediate(),
176-
restMethod(method),
177-
Collections.emptyList());
178-
}
179-
180157
/**
181158
* Returns the parameterized of the request type of a given object.
182159
*
@@ -384,15 +361,27 @@ public static boolean isService(Class<?> type) {
384361
}
385362

386363
/**
387-
* Checks whether given method is considered secured, i.e does it have annotation {@link Secured},
388-
* or, if not, then does declaring class contains annotation {@link Secured}.
364+
* Retrives {@link Secured} annotation. Annotation is expected to be declared on service method,
365+
* or on the service class. If declared on both - service method declaration takes precedence.
389366
*
390-
* @param method method
391-
* @return result
367+
* @param method service method
368+
* @return {@link Secured} annotation, or null
392369
*/
393-
public static boolean isSecured(Method method) {
394-
return method.isAnnotationPresent(Secured.class)
395-
|| method.getDeclaringClass().isAnnotationPresent(Secured.class);
370+
public static Secured secured(Method method) {
371+
Secured secured = method.getAnnotation(Secured.class);
372+
373+
// If @Secured annotation is not present on service method, then find it on service class
374+
375+
if (secured == null) {
376+
for (var clazz = method.getDeclaringClass(); clazz != null; clazz = clazz.getSuperclass()) {
377+
if (clazz.isAnnotationPresent(Secured.class)) {
378+
secured = clazz.getAnnotation(Secured.class);
379+
break;
380+
}
381+
}
382+
}
383+
384+
return secured;
396385
}
397386

398387
/**

services-api/src/main/java/io/scalecube/services/RequestContext.java

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,10 @@ public static Mono<RequestContext> deferSecured() {
319319
final var principal = context.principal();
320320
final var methodInfo = context.methodInfo();
321321

322-
if (!methodInfo.allowedRoles().contains(principal.role())) {
322+
final var allowedRoles = methodInfo.allowedRoles();
323+
if (allowedRoles != null
324+
&& !allowedRoles.isEmpty()
325+
&& !allowedRoles.contains(principal.role())) {
323326
LOGGER.warn(
324327
"Insufficient permissions for secured method ({}) -- "
325328
+ "principal role '{}' is not allowed (principal: {})",
@@ -329,15 +332,18 @@ public static Mono<RequestContext> deferSecured() {
329332
throw new ForbiddenException("Insufficient permissions");
330333
}
331334

332-
for (var allowedPermission : methodInfo.allowedPermissions()) {
333-
if (!principal.hasPermission(allowedPermission)) {
334-
LOGGER.warn(
335-
"Insufficient permissions for secured method ({}) -- "
336-
+ "allowed permission '{}' is missing (principal: {})",
337-
context.methodInfo(),
338-
allowedPermission,
339-
principal);
340-
throw new ForbiddenException("Insufficient permissions");
335+
final var allowedPermissions = methodInfo.allowedPermissions();
336+
if (allowedPermissions != null && !allowedPermissions.isEmpty()) {
337+
for (var allowedPermission : allowedPermissions) {
338+
if (!principal.hasPermission(allowedPermission)) {
339+
LOGGER.warn(
340+
"Insufficient permissions for secured method ({}) -- "
341+
+ "allowed permission '{}' is missing (principal: {})",
342+
context.methodInfo(),
343+
allowedPermission,
344+
principal);
345+
throw new ForbiddenException("Insufficient permissions");
346+
}
341347
}
342348
}
343349
});

services-api/src/main/java/io/scalecube/services/ServiceCall.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
package io.scalecube.services;
22

3+
import static io.scalecube.services.Reflect.communicationMode;
4+
import static io.scalecube.services.Reflect.isRequestTypeServiceMessage;
5+
import static io.scalecube.services.Reflect.isReturnTypeServiceMessage;
6+
import static io.scalecube.services.Reflect.methodName;
7+
import static io.scalecube.services.Reflect.parameterizedReturnType;
8+
import static io.scalecube.services.Reflect.requestType;
9+
import static io.scalecube.services.Reflect.restMethod;
10+
import static io.scalecube.services.Reflect.serviceName;
311
import static io.scalecube.services.auth.Principal.NULL_PRINCIPAL;
412

513
import io.scalecube.services.api.ErrorData;
@@ -13,6 +21,7 @@
1321
import io.scalecube.services.routing.Router;
1422
import io.scalecube.services.routing.Routers;
1523
import io.scalecube.services.transport.api.ClientTransport;
24+
import java.lang.reflect.Method;
1625
import java.lang.reflect.Proxy;
1726
import java.lang.reflect.Type;
1827
import java.util.Collections;
@@ -27,6 +36,7 @@
2736
import reactor.core.Exceptions;
2837
import reactor.core.publisher.Flux;
2938
import reactor.core.publisher.Mono;
39+
import reactor.core.scheduler.Schedulers;
3040

3141
public class ServiceCall implements AutoCloseable {
3242

@@ -380,7 +390,7 @@ public <T> T api(Class<T> serviceInterface) {
380390
}
381391

382392
final var serviceCall = this;
383-
final var methodInfo = Reflect.methodInfo(serviceInterface, method);
393+
final var methodInfo = getMethodInfo(serviceInterface, method);
384394
final var returnType = methodInfo.parameterizedReturnType();
385395
final var isReturnTypeServiceMessage = methodInfo.isReturnTypeServiceMessage();
386396
final var request = methodInfo.requestType() == Void.TYPE ? null : params[0];
@@ -487,6 +497,22 @@ private ServiceMessage throwIfError(ServiceMessage message) {
487497
return message;
488498
}
489499

500+
private static MethodInfo getMethodInfo(Class<?> serviceInterface, Method method) {
501+
return new MethodInfo(
502+
serviceName(serviceInterface),
503+
methodName(method),
504+
parameterizedReturnType(method),
505+
isReturnTypeServiceMessage(method),
506+
communicationMode(method),
507+
method.getParameterCount(),
508+
requestType(method),
509+
isRequestTypeServiceMessage(method),
510+
null,
511+
Schedulers.immediate(),
512+
restMethod(method),
513+
Collections.emptyList());
514+
}
515+
490516
@Override
491517
public void close() {
492518
if (transport != null) {

services-api/src/main/java/io/scalecube/services/auth/Secured.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,13 @@
1515
@Documented
1616
@Target({METHOD, TYPE})
1717
@Retention(RUNTIME)
18-
public @interface Secured {}
18+
public @interface Secured {
19+
20+
/**
21+
* Returns whether service method must apply standard security check behavior.
22+
*
23+
* @return {@code true} if service must apply standard security check behavior, {@code false}
24+
* otherwise
25+
*/
26+
boolean deferSecured() default true;
27+
}

services-api/src/main/java/io/scalecube/services/methods/MethodInfo.java

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import io.scalecube.services.CommunicationMode;
66
import io.scalecube.services.api.DynamicQualifier;
77
import io.scalecube.services.api.Qualifier;
8+
import io.scalecube.services.auth.Secured;
89
import java.lang.reflect.Type;
910
import java.util.Collection;
1011
import java.util.StringJoiner;
@@ -23,10 +24,9 @@ public class MethodInfo {
2324
private final int parameterCount;
2425
private final Class<?> requestType;
2526
private final boolean isRequestTypeServiceMessage;
26-
private final boolean isSecured;
27+
private final Secured secured;
2728
private final Scheduler scheduler;
2829
private final String restMethod;
29-
private final Collection<ServiceRoleDefinition> serviceRoles;
3030
private final Collection<String> allowedRoles;
3131
private final Collection<String> allowedPermissions;
3232

@@ -41,7 +41,7 @@ public class MethodInfo {
4141
* @param parameterCount amount of parameters
4242
* @param requestType the type of the request
4343
* @param isRequestTypeServiceMessage is request service message
44-
* @param isSecured is method protected by authentication
44+
* @param secured secured (optional)
4545
* @param scheduler scheduler (optional)
4646
* @param restMethod restMethod (optional)
4747
* @param serviceRoles serviceRoles (optional)
@@ -55,7 +55,7 @@ public MethodInfo(
5555
int parameterCount,
5656
Class<?> requestType,
5757
boolean isRequestTypeServiceMessage,
58-
boolean isSecured,
58+
Secured secured,
5959
Scheduler scheduler,
6060
String restMethod,
6161
Collection<ServiceRoleDefinition> serviceRoles) {
@@ -69,10 +69,9 @@ public MethodInfo(
6969
this.parameterCount = parameterCount;
7070
this.requestType = requestType;
7171
this.isRequestTypeServiceMessage = isRequestTypeServiceMessage;
72-
this.isSecured = isSecured;
72+
this.secured = secured;
7373
this.scheduler = scheduler;
7474
this.restMethod = restMethod;
75-
this.serviceRoles = serviceRoles;
7675
this.allowedRoles =
7776
serviceRoles.stream().map(ServiceRoleDefinition::role).collect(Collectors.toSet());
7877
this.allowedPermissions =
@@ -125,8 +124,8 @@ public Class<?> requestType() {
125124
return requestType;
126125
}
127126

128-
public boolean isSecured() {
129-
return isSecured;
127+
public Secured secured() {
128+
return secured;
130129
}
131130

132131
public Scheduler scheduler() {
@@ -137,10 +136,6 @@ public String restMethod() {
137136
return restMethod;
138137
}
139138

140-
public Collection<ServiceRoleDefinition> serviceRoles() {
141-
return serviceRoles;
142-
}
143-
144139
public Collection<String> allowedRoles() {
145140
return allowedRoles;
146141
}
@@ -162,10 +157,9 @@ public String toString() {
162157
.add("parameterCount=" + parameterCount)
163158
.add("requestType=" + requestType)
164159
.add("isRequestTypeServiceMessage=" + isRequestTypeServiceMessage)
165-
.add("isSecured=" + isSecured)
160+
.add("secured=" + secured)
166161
.add("scheduler=" + scheduler)
167162
.add("restMethod=" + restMethod)
168-
.add("serviceRoles=" + serviceRoles)
169163
.add("allowedRoles=" + allowedRoles)
170164
.add("allowedPermissions=" + allowedPermissions)
171165
.toString();

services-api/src/main/java/io/scalecube/services/methods/ServiceMethodInvoker.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ public Mono<ServiceMessage> invokeOne(ServiceMessage message) {
6363
return mapPrincipal(context)
6464
.flatMap(
6565
principal ->
66-
Mono.defer(() -> Mono.from(invokeRequest(request)))
66+
requestContext()
67+
.then(Mono.defer(() -> Mono.from(invokeRequest(request))))
6768
.contextWrite(enhanceRequestContext(context, request, principal)))
6869
.doOnSuccess(
6970
response -> {
@@ -104,7 +105,8 @@ public Flux<ServiceMessage> invokeMany(ServiceMessage message) {
104105
return mapPrincipal(context)
105106
.flatMapMany(
106107
principal ->
107-
Flux.defer(() -> Flux.from(invokeRequest(request)))
108+
requestContext()
109+
.thenMany(Flux.defer(() -> Flux.from(invokeRequest(request))))
108110
.contextWrite(enhanceRequestContext(context, request, principal)))
109111
.doOnSubscribe(
110112
s -> {
@@ -153,6 +155,19 @@ public Flux<ServiceMessage> invokeBidirectional(Publisher<ServiceMessage> publis
153155
});
154156
}
155157

158+
private Mono<RequestContext> requestContext() {
159+
final Mono<RequestContext> contextMono;
160+
final var secured = methodInfo.secured();
161+
162+
if (secured != null && secured.deferSecured()) {
163+
contextMono = RequestContext.deferSecured();
164+
} else {
165+
contextMono = RequestContext.deferContextual();
166+
}
167+
168+
return contextMono;
169+
}
170+
156171
private Publisher<?> invokeRequest(Object request) {
157172
Publisher<?> result = null;
158173
Throwable throwable = null;
@@ -230,7 +245,7 @@ public PrincipalMapper principalMapper() {
230245
}
231246

232247
private Mono<Principal> mapPrincipal(RequestContext context) {
233-
if (!methodInfo.isSecured()) {
248+
if (methodInfo.secured() == null) {
234249
return Mono.just(context.principal());
235250
}
236251

services-api/src/test/java/io/scalecube/services/methods/PrincipalImpl.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.scalecube.services.auth.Principal;
44
import java.util.List;
55
import java.util.Objects;
6+
import java.util.StringJoiner;
67

78
public record PrincipalImpl(String role, List<String> permissions) implements Principal {
89

@@ -15,4 +16,12 @@ public boolean hasRole(String role) {
1516
public boolean hasPermission(String permission) {
1617
return permissions != null && permissions.contains(permission);
1718
}
19+
20+
@Override
21+
public String toString() {
22+
return new StringJoiner(", ", PrincipalImpl.class.getSimpleName() + "[", "]")
23+
.add("role='" + role + "'")
24+
.add("permissions=" + permissions)
25+
.toString();
26+
}
1827
}

0 commit comments

Comments
 (0)