Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -331,19 +331,20 @@ public static Mono<RequestContext> deferSecured() {
final var principal = context.principal();
final var methodInfo = context.methodInfo();

final var role = principal.role();
final var allowedRoles = methodInfo.allowedRoles();
if (allowedRoles != null
&& !allowedRoles.isEmpty()
&& !allowedRoles.contains(principal.role())) {
LOGGER.warn(
"Insufficient permissions -- "
+ "principal role '{}' is not allowed, request context: {}",
principal.role(),
role,
context);
throw new ForbiddenException("Insufficient permissions");
}

final var allowedPermissions = methodInfo.allowedPermissions();
final var allowedPermissions = methodInfo.allowedPermissions(role);
if (allowedPermissions != null && !allowedPermissions.isEmpty()) {
for (var allowedPermission : allowedPermissions) {
if (!principal.hasPermission(allowedPermission)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.scalecube.services.auth.Secured;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import reactor.core.scheduler.Scheduler;
Expand All @@ -27,8 +28,7 @@ public class MethodInfo {
private final Secured secured;
private final Scheduler scheduler;
private final String restMethod;
private final Collection<String> allowedRoles;
private final Collection<String> allowedPermissions;
private final Map<String, Collection<String>> allowedRoles;

/**
* Create a new service info.
Expand Down Expand Up @@ -73,11 +73,9 @@ public MethodInfo(
this.scheduler = scheduler;
this.restMethod = restMethod;
this.allowedRoles =
serviceRoles.stream().map(ServiceRoleDefinition::role).collect(Collectors.toSet());
this.allowedPermissions =
serviceRoles.stream()
.flatMap(definition -> definition.permissions().stream())
.collect(Collectors.toSet());
.collect(
Collectors.toMap(ServiceRoleDefinition::role, ServiceRoleDefinition::permissions));
}

public String serviceName() {
Expand Down Expand Up @@ -137,11 +135,11 @@ public String restMethod() {
}

public Collection<String> allowedRoles() {
return allowedRoles;
return allowedRoles.keySet();
}

public Collection<String> allowedPermissions() {
return allowedPermissions;
public Collection<String> allowedPermissions(String role) {
return allowedRoles.get(role);
}

@Override
Expand All @@ -161,7 +159,6 @@ public String toString() {
.add("scheduler=" + scheduler)
.add("restMethod=" + restMethod)
.add("allowedRoles=" + allowedRoles)
.add("allowedPermissions=" + allowedPermissions)
.toString();
}
}
58 changes: 50 additions & 8 deletions services/src/test/java/io/scalecube/services/AuthTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.params.provider.Arguments.arguments;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
Expand Down Expand Up @@ -278,6 +278,50 @@ void authenticateSuccessfully() {
StepVerifier.create(serviceCall.api(SecuredService.class).writeWithAllowedRoleAnnotation())
.verifyComplete();
}

@ParameterizedTest
@MethodSource("multipleRolesSuccessfulSource")
void authenticateSuccessfullyByMultipleRoles(String role, TokenCredentials tokenCredentials) {
serviceCall = serviceCall((service, roles) -> credentials(tokenCredentials), role);
StepVerifier.create(serviceCall.api(SecuredService.class).invokeByMultipleRoles())
.verifyComplete();
}

@ParameterizedTest
@MethodSource("multipleRolesFailedSource")
void authenticationFailedByMultipleRoles(String role, TokenCredentials tokenCredentials) {
serviceCall = serviceCall((service, roles) -> credentials(tokenCredentials), role);
StepVerifier.create(serviceCall.api(SecuredService.class).invokeByMultipleRoles())
.verifyErrorSatisfies(
ex -> {
final var exception = (ForbiddenException) ex;
assertEquals(403, exception.errorCode(), "errorCode");
assertEquals("Insufficient permissions", exception.getMessage(), "errorMessage");
});
}

private static Stream<Arguments> multipleRolesSuccessfulSource() {
return Stream.of(
Arguments.of(
"gateway",
new TokenCredentials(
VALID_TOKEN, "gateway", List.of("gateway:read", "gateway:write"))),
Arguments.of(
"operations",
new TokenCredentials(
VALID_TOKEN, "operations", List.of("operations:read", "operations:write"))));
}

private static Stream<Arguments> multipleRolesFailedSource() {
return Stream.of(
Arguments.of(
"gateway",
new TokenCredentials(VALID_TOKEN, "gateway", List.of("gateway:non-exiting-list"))),
Arguments.of(
"operations",
new TokenCredentials(
VALID_TOKEN, "operations", List.of("operations:non-exiting-list"))));
}
}

private static Mono<byte[]> credentials(TokenCredentials tokenCredentials) {
Expand Down Expand Up @@ -354,13 +398,11 @@ private static ObjectMapper initMapper() {
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
mapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.setVisibility(PropertyAccessor.ALL, Visibility.ANY);
mapper.setSerializationInclusion(Include.NON_NULL);
mapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true);
mapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
DefaultTyping.JAVA_LANG_OBJECT,
JsonTypeInfo.As.WRAPPER_OBJECT);
LaissezFaireSubTypeValidator.instance, DefaultTyping.JAVA_LANG_OBJECT, As.WRAPPER_OBJECT);
mapper.findAndRegisterModules();
return mapper;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,9 @@ public interface SecuredService {

@ServiceMethod
Mono<Void> writeWithAllowedRoleAnnotation();

// Multiple roles

@ServiceMethod
Mono<String> invokeByMultipleRoles();
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,16 @@ public Mono<Void> readWithAllowedRoleAnnotation() {
public Mono<Void> writeWithAllowedRoleAnnotation() {
return Mono.empty();
}

@Secured
@AllowedRole(
name = "gateway",
permissions = {"gateway:read"})
@AllowedRole(
name = "operations",
permissions = {"operations:read"})
@Override
public Mono<String> invokeByMultipleRoles() {
return Mono.empty();
}
}