From c34d2d8bfc5b881467d5c768780028320bba1859 Mon Sep 17 00:00:00 2001 From: Robert Csakany Date: Thu, 21 Nov 2024 16:16:12 +0100 Subject: [PATCH] JNG-6017 Extending auth interceptor api (#218) --- .../api/AuthenticationInterceptor.java | 46 +++++++++++++++++-- .../accessmanager/DefaultAccessManager.java | 19 ++++---- .../core/dispatcher/DefaultActorResolver.java | 23 +++++++++- .../core/dispatcher/DefaultDispatcher.java | 3 ++ .../DefaultActorResolverProvider.java | 5 ++ .../providers/PayloadMessageBodyWriter.java | 7 ++- .../JudoDefaultSpringConfiguration.java | 1 + 7 files changed, 89 insertions(+), 15 deletions(-) diff --git a/judo-runtime-core-accessmanager-api/src/main/java/hu/blackbelt/judo/runtime/core/accessmanager/api/AuthenticationInterceptor.java b/judo-runtime-core-accessmanager-api/src/main/java/hu/blackbelt/judo/runtime/core/accessmanager/api/AuthenticationInterceptor.java index ae8e3e8f..92b8105d 100644 --- a/judo-runtime-core-accessmanager-api/src/main/java/hu/blackbelt/judo/runtime/core/accessmanager/api/AuthenticationInterceptor.java +++ b/judo-runtime-core-accessmanager-api/src/main/java/hu/blackbelt/judo/runtime/core/accessmanager/api/AuthenticationInterceptor.java @@ -4,18 +4,56 @@ import java.util.Map; +/** + * This interface defines an interceptor for authentication / authorization call. + */ public interface AuthenticationInterceptor { String getName(); - boolean isSuitableForOperation(EOperation operation, final String claim, - final String realm, final String client, Map attributes); + default boolean isSuitableForOperation(EOperation operation, final String claim, + final String realm, final String client, Map attributes) { + return true; + } - void success(final EOperation operation, + /** + * It is called after the extraction of principal but before the load of the mapped principal load. + * CAUTION! From the implementation of this method expressions which uses actor cannot be used. + * (for example getVariables for actor). + * + * @param operationFullyQualifiedName The called operation's fully qualified name + * @param exchange The request exchange + * @param claim The user's name + * @param realm Realm authenticated + * @param client Authenticated client + * @param attributes Token attributes + */ + default void authenticate(final String operationFullyQualifiedName, + final Map exchange, + final String claim, + final String realm, + final String client, + final Map attributes + ) {}; + + + /** + * It is called after successfully authorization, but before operation call,when the given operation have to be checked for + * authorization. + * + * @param operation The called operation's fully qualified name + * @param signedIdentifier The mapped operation's owner signed identifier + * @param exchange The request exchange + * @param claim The user's name + * @param realm Realm authenticated + * @param client Authenticated client + * @param attributes Token attributes + */ + default void success(final EOperation operation, final SignedIdentifier signedIdentifier, final Map exchange, final String claim, final String realm, final String client, final Map attributes - ); + ) {}; } diff --git a/judo-runtime-core-accessmanager/src/main/java/hu/blackbelt/judo/runtime/core/accessmanager/DefaultAccessManager.java b/judo-runtime-core-accessmanager/src/main/java/hu/blackbelt/judo/runtime/core/accessmanager/DefaultAccessManager.java index 8f046aee..329a282c 100644 --- a/judo-runtime-core-accessmanager/src/main/java/hu/blackbelt/judo/runtime/core/accessmanager/DefaultAccessManager.java +++ b/judo-runtime-core-accessmanager/src/main/java/hu/blackbelt/judo/runtime/core/accessmanager/DefaultAccessManager.java @@ -38,6 +38,7 @@ import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EOperation; +import java.security.Principal; import java.util.*; import java.util.stream.Collectors; @@ -92,8 +93,8 @@ public void authorizeOperation(final EOperation operation, final SignedIdentifie throw new IllegalStateException("Unsupported principal"); } - final JudoPrincipal principal = exchange.get(Dispatcher.PRINCIPAL_KEY) != null ? (JudoPrincipal) exchange.get(Dispatcher.PRINCIPAL_KEY) : null; - final String actorFqName = principal != null ? principal.getClient() : null; + final Principal principal = exchange.get(Dispatcher.PRINCIPAL_KEY) != null ? (Principal) exchange.get(Dispatcher.PRINCIPAL_KEY) : null; + final String actorFqName = principal != null && principal instanceof JudoPrincipal ? ((JudoPrincipal) principal).getClient() : null; final boolean exposedForPublicOrTokenActor = AsmUtils.getExtensionAnnotationListByName(operation, "exposedBy").stream() .anyMatch(a -> publicActors.contains(a.getDetails().get("value")) || Objects.equals(actorFqName, a.getDetails().get("value"))); @@ -131,21 +132,23 @@ public void authorizeOperation(final EOperation operation, final SignedIdentifie } if (authenticationInterceptorProvider != null && principal != null) { + + JudoPrincipal judoPrincipal = principal instanceof JudoPrincipal ? (JudoPrincipal) principal : null; authenticationInterceptorProvider.getAuthenticationInterceptors().stream() .filter(authenticationInterceptor -> authenticationInterceptor.isSuitableForOperation( operation, principal.getName(), - principal.getRealm(), - principal.getClient(), - principal.getAttributes())) + judoPrincipal != null ? judoPrincipal.getRealm() : null, + judoPrincipal != null ? judoPrincipal.getClient(): null, + judoPrincipal != null ? judoPrincipal.getAttributes() : null)) .forEach(authenticationInterceptor -> { authenticationInterceptor.success(operation, signedIdentifier, exchange, principal.getName(), - principal.getRealm(), - principal.getClient(), - principal.getAttributes()); + judoPrincipal != null ? judoPrincipal.getRealm() : null, + judoPrincipal != null ? judoPrincipal.getClient() : null, + judoPrincipal != null ? judoPrincipal.getAttributes() : null); }); } diff --git a/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/DefaultActorResolver.java b/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/DefaultActorResolver.java index 7b5d822c..d3890be2 100644 --- a/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/DefaultActorResolver.java +++ b/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/DefaultActorResolver.java @@ -28,6 +28,7 @@ import hu.blackbelt.judo.meta.asm.runtime.AsmModel; import hu.blackbelt.judo.meta.asm.runtime.AsmUtils; import hu.blackbelt.judo.runtime.core.DataTypeManager; +import hu.blackbelt.judo.runtime.core.accessmanager.api.AuthenticationInterceptorProvider; import hu.blackbelt.judo.runtime.core.dispatcher.behaviours.QueryCustomizerParameterProcessor; import hu.blackbelt.judo.runtime.core.dispatcher.security.ActorResolver; import hu.blackbelt.judo.runtime.core.exception.AccessDeniedException; @@ -40,6 +41,7 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.security.Principal; import java.time.*; import java.util.*; import java.util.stream.Collectors; @@ -59,23 +61,42 @@ public class DefaultActorResolver implements ActorResolver { private AsmUtils asmUtils; + AuthenticationInterceptorProvider authenticationInterceptorProvider; + @Builder public DefaultActorResolver( @NonNull DataTypeManager dataTypeManager, @NonNull DAO dao, @NonNull AsmModel asmModel, + AuthenticationInterceptorProvider authenticationInterceptorProvider, Boolean checkMappedActors) { this.dataTypeManager = dataTypeManager; this.dao = dao; this.asmModel = asmModel; this.checkMappedActors = checkMappedActors == null ? false : checkMappedActors; this.asmUtils = new AsmUtils(asmModel.getResourceSet()); + this.authenticationInterceptorProvider = authenticationInterceptorProvider; } @Override public void authenticateActor(final Map exchange) { - final Object principal = exchange.get(Dispatcher.PRINCIPAL_KEY); + final Principal principal = (Principal) exchange.get(Dispatcher.PRINCIPAL_KEY); + + final String operationFullyQualifiedName = (String) exchange.get("__operationFullyQualifiedName"); + if (authenticationInterceptorProvider != null && + operationFullyQualifiedName != null && principal != null) { + JudoPrincipal judoPrincipal = principal instanceof JudoPrincipal ? (JudoPrincipal) principal : null; + authenticationInterceptorProvider.getAuthenticationInterceptors().stream() + .forEach(authenticationInterceptor -> { + authenticationInterceptor.authenticate(operationFullyQualifiedName, + exchange, + principal.getName(), + judoPrincipal != null ? judoPrincipal.getRealm() : null, + judoPrincipal != null ? judoPrincipal.getClient() : null, + judoPrincipal != null ? judoPrincipal.getAttributes() : null); + }); + } if ((principal instanceof JudoPrincipal) && !exchange.containsKey(Dispatcher.ACTOR_KEY) && checkMappedActors) { final Optional actor = authenticateByPrincipal((JudoPrincipal) principal); diff --git a/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/DefaultDispatcher.java b/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/DefaultDispatcher.java index ff191a3d..31e99394 100644 --- a/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/DefaultDispatcher.java +++ b/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/DefaultDispatcher.java @@ -618,7 +618,10 @@ public Map callOperation(final String operationFullyQualifiedNam } try (MetricsCancelToken ignored = metricsCollector.start(METRICS_DISPATCHER)) { + exchange.put("__operationFullyQualifiedName", operationFullyQualifiedName); actorResolver.authenticateActor(exchange); + exchange.remove("__operationFullyQualifiedName"); + if (exchange.containsKey(ACTOR_KEY)) { context.putIfAbsent(ACTOR_KEY, exchange.get(ACTOR_KEY)); } diff --git a/judo-runtime-core-guice/src/main/java/hu/blackbelt/judo/runtime/core/guice/dispatcher/DefaultActorResolverProvider.java b/judo-runtime-core-guice/src/main/java/hu/blackbelt/judo/runtime/core/guice/dispatcher/DefaultActorResolverProvider.java index 877da7c7..71515cdd 100644 --- a/judo-runtime-core-guice/src/main/java/hu/blackbelt/judo/runtime/core/guice/dispatcher/DefaultActorResolverProvider.java +++ b/judo-runtime-core-guice/src/main/java/hu/blackbelt/judo/runtime/core/guice/dispatcher/DefaultActorResolverProvider.java @@ -26,6 +26,7 @@ import hu.blackbelt.judo.dao.api.DAO; import hu.blackbelt.judo.meta.asm.runtime.AsmModel; import hu.blackbelt.judo.runtime.core.DataTypeManager; +import hu.blackbelt.judo.runtime.core.accessmanager.api.AuthenticationInterceptorProvider; import hu.blackbelt.judo.runtime.core.dispatcher.DefaultActorResolver; import hu.blackbelt.judo.runtime.core.dispatcher.security.ActorResolver; @@ -45,6 +46,9 @@ public class DefaultActorResolverProvider implements Provider { @Inject DataTypeManager dataTypeManager; + @Inject + AuthenticationInterceptorProvider authenticationInterceptorProvider; + @Inject(optional = true) @Named(ACTOR_RESOLVER_CHECK_MAPPED_ACTORS) @Nullable @@ -58,6 +62,7 @@ public ActorResolver get() { .dao(dao) .asmModel(asmModel) .checkMappedActors(checkMappedActors) + .authenticationInterceptorProvider(authenticationInterceptorProvider) .build(); } } diff --git a/judo-runtime-core-jaxrs/src/main/java/hu/blackbelt/judo/runtime/core/jaxrs/providers/PayloadMessageBodyWriter.java b/judo-runtime-core-jaxrs/src/main/java/hu/blackbelt/judo/runtime/core/jaxrs/providers/PayloadMessageBodyWriter.java index 93819c21..e7f0ec29 100644 --- a/judo-runtime-core-jaxrs/src/main/java/hu/blackbelt/judo/runtime/core/jaxrs/providers/PayloadMessageBodyWriter.java +++ b/judo-runtime-core-jaxrs/src/main/java/hu/blackbelt/judo/runtime/core/jaxrs/providers/PayloadMessageBodyWriter.java @@ -33,7 +33,10 @@ public boolean isWriteable(Class type, Type genericType, Annotation[] annotat @Override public void writeTo(PayloadImpl payload, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { - OutputStreamWriter outputStreamWriter = new OutputStreamWriter(entityStream); - objectMapper.writeValue(outputStreamWriter, payload); + try { + OutputStreamWriter outputStreamWriter = new OutputStreamWriter(entityStream); + objectMapper.writeValue(outputStreamWriter, payload); + } catch (Throwable throwable) { + } } } diff --git a/judo-runtime-core-spring/src/main/java/hu/blackbelt/judo/runtime/core/spring/JudoDefaultSpringConfiguration.java b/judo-runtime-core-spring/src/main/java/hu/blackbelt/judo/runtime/core/spring/JudoDefaultSpringConfiguration.java index 042a0123..2c5d2610 100644 --- a/judo-runtime-core-spring/src/main/java/hu/blackbelt/judo/runtime/core/spring/JudoDefaultSpringConfiguration.java +++ b/judo-runtime-core-spring/src/main/java/hu/blackbelt/judo/runtime/core/spring/JudoDefaultSpringConfiguration.java @@ -300,6 +300,7 @@ public ActorResolver getActorResolver( .dao(dao) .asmModel(asmModel) .checkMappedActors(checkMappedActors) + .authenticationInterceptorProvider(authenticationInterceptorProvider) .build(); }