From 3b02c4ab1dbed7fce597bdbe5ca0b00d1afb71ea Mon Sep 17 00:00:00 2001
From: Hamza Remmal <hamzaremmalpriv@gmail.com>
Date: Thu, 10 Aug 2023 14:22:00 +0100
Subject: [PATCH] Add possibility to have multiple Authentication based on the
 request + add publisher to publish the authentication events

---
 .idea/encodings.xml                           |  1 +
 .idea/misc.xml                                |  2 +
 .../AutogradeAuthenticationConverter.java     | 26 ++++++++++++
 .../auth/events/AuthenticationEvents.java     | 31 ++++++++++++++
 .../filter/ApiKeyAuthenticationFilter.java    | 41 ++++++++++++++-----
 .../ApiKeyAuthenticationProvider.java         |  2 +-
 .../config/AuthenticationConfiguration.java   | 23 +++++++++++
 7 files changed, 114 insertions(+), 12 deletions(-)
 create mode 100644 autograde-service/src/main/java/ch/epfl/autograde/auth/converter/AutogradeAuthenticationConverter.java
 create mode 100644 autograde-service/src/main/java/ch/epfl/autograde/auth/events/AuthenticationEvents.java
 create mode 100644 autograde-service/src/main/java/ch/epfl/autograde/config/AuthenticationConfiguration.java

diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index 0903db4a..cc6b8358 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="Encoding">
+    <file url="file://$PROJECT_DIR$/autograde-service/src/main/java" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/moodle-grading-service/src/main/java" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/moodle-grading-service/src/main/resources" charset="UTF-8" />
   </component>
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 99e50705..6a1cc841 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,9 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="ExternalStorageConfigurationManager" enabled="true" />
   <component name="MavenProjectsManager">
     <option name="originalFiles">
       <list>
         <option value="$PROJECT_DIR$/moodle-grading-service/pom.xml" />
+        <option value="$PROJECT_DIR$/autograde-service/pom.xml" />
       </list>
     </option>
   </component>
diff --git a/autograde-service/src/main/java/ch/epfl/autograde/auth/converter/AutogradeAuthenticationConverter.java b/autograde-service/src/main/java/ch/epfl/autograde/auth/converter/AutogradeAuthenticationConverter.java
new file mode 100644
index 00000000..ecf68f2e
--- /dev/null
+++ b/autograde-service/src/main/java/ch/epfl/autograde/auth/converter/AutogradeAuthenticationConverter.java
@@ -0,0 +1,26 @@
+package ch.epfl.autograde.auth.converter;
+
+import ch.epfl.autograde.auth.ApiKeyAuthentication;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.AuthenticationConverter;
+import org.springframework.stereotype.Component;
+
+/**
+ * ???
+ *
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
+@Component
+public final class AutogradeAuthenticationConverter implements AuthenticationConverter {
+
+    private static final String API_KEY_HEADER_ENTRY = "API-KEY";
+
+    @Override
+    public Authentication convert(HttpServletRequest request) {
+        // HR : Fetch the API-KEY from the header
+        var key = request.getHeader(API_KEY_HEADER_ENTRY);
+        return new ApiKeyAuthentication(key, false);
+    }
+
+}
diff --git a/autograde-service/src/main/java/ch/epfl/autograde/auth/events/AuthenticationEvents.java b/autograde-service/src/main/java/ch/epfl/autograde/auth/events/AuthenticationEvents.java
new file mode 100644
index 00000000..2850bb9f
--- /dev/null
+++ b/autograde-service/src/main/java/ch/epfl/autograde/auth/events/AuthenticationEvents.java
@@ -0,0 +1,31 @@
+package ch.epfl.autograde.auth.events;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.event.EventListener;
+import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
+import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent;
+import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
+import org.springframework.stereotype.Component;
+
+/**
+ * ???
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ * @since 1.0
+ * @see <a href="https://docs.spring.io/spring-security/reference/servlet/authentication/events.html">docs.spring.io</a>
+ */
+@Slf4j
+@Component
+public final class AuthenticationEvents {
+
+    @EventListener
+    public void onSuccess(AuthenticationSuccessEvent success) {
+        // TODO HR : DO NOT IMPLEMENT IT, CAN BE QUITE NOISY IN THE LOGS
+    }
+
+    @EventListener
+    public void onFailure(AuthenticationFailureBadCredentialsEvent failures) {
+        log.error("An authentication request has failed from source {}", failures.getSource(), failures.getException());
+        // ...
+    }
+
+}
diff --git a/autograde-service/src/main/java/ch/epfl/autograde/auth/filter/ApiKeyAuthenticationFilter.java b/autograde-service/src/main/java/ch/epfl/autograde/auth/filter/ApiKeyAuthenticationFilter.java
index f4241e13..36f302c4 100644
--- a/autograde-service/src/main/java/ch/epfl/autograde/auth/filter/ApiKeyAuthenticationFilter.java
+++ b/autograde-service/src/main/java/ch/epfl/autograde/auth/filter/ApiKeyAuthenticationFilter.java
@@ -1,12 +1,16 @@
 package ch.epfl.autograde.auth.filter;
 
-import ch.epfl.autograde.auth.ApiKeyAuthentication;
+import ch.epfl.autograde.auth.converter.AutogradeAuthenticationConverter;
 import ch.epfl.autograde.auth.manager.ApiKeyAuthenticationManager;
 import jakarta.servlet.FilterChain;
 import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationEventPublisher;
+import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.stereotype.Component;
 import org.springframework.web.filter.OncePerRequestFilter;
@@ -18,21 +22,28 @@ import java.io.IOException;
  *
  * @author Hamza REMMAL (hamza.remmal@epfl.ch)
  */
+@Slf4j
 @Component
-public class ApiKeyAuthenticationFilter extends OncePerRequestFilter {
+public final class ApiKeyAuthenticationFilter extends OncePerRequestFilter {
 
-    private static final String API_KEY_HEADER_ENTRY = "API-KEY";
+    private final AutogradeAuthenticationConverter converter;
 
     /** ??? */
     private final ApiKeyAuthenticationManager manager;
 
+    private final AuthenticationEventPublisher publisher;
+
     /**
      * ???
      * @param manager ???
      */
     @Autowired
-    public ApiKeyAuthenticationFilter(ApiKeyAuthenticationManager manager) {
+    public ApiKeyAuthenticationFilter(ApiKeyAuthenticationManager manager,
+                                      AutogradeAuthenticationConverter converter,
+                                      AuthenticationEventPublisher publisher) {
         this.manager = manager;
+        this.converter = converter;
+        this.publisher = publisher;
     }
 
     /**
@@ -44,13 +55,21 @@ public class ApiKeyAuthenticationFilter extends OncePerRequestFilter {
      * @throws IOException ???
      */
     @Override
-    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
-        // HR : Fetch the API-KEY from the header
-        var key = request.getHeader(API_KEY_HEADER_ENTRY);
-        // HR : Request the authentication from the authentication manager
-        var auth = manager.authenticate(new ApiKeyAuthentication(key, false));
-        // HR : Set the authentication in the SecurityContext
-        SecurityContextHolder.getContext().setAuthentication(auth);
+    protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+        // HR : Fetch the authentication object based on the request
+        var authentication = converter.convert(request);
+        try {
+            // HR : Authenticate using the manager
+            var auth = manager.authenticate(authentication);
+            // HR : Set the authentication in the SecurityContext
+            SecurityContextHolder.getContext().setAuthentication(auth);
+            // HR : Inform the publisher
+            publisher.publishAuthenticationSuccess(authentication);
+        }catch (AuthenticationException e){
+            log.trace("Authentication using the filter failed", e);
+            // hr : Inform the publisher
+            publisher.publishAuthenticationFailure(e, authentication);
+        }
         // HR : Complete the chain of filters
         filterChain.doFilter(request, response);
     }
diff --git a/autograde-service/src/main/java/ch/epfl/autograde/auth/provider/ApiKeyAuthenticationProvider.java b/autograde-service/src/main/java/ch/epfl/autograde/auth/provider/ApiKeyAuthenticationProvider.java
index 09125953..414e28db 100644
--- a/autograde-service/src/main/java/ch/epfl/autograde/auth/provider/ApiKeyAuthenticationProvider.java
+++ b/autograde-service/src/main/java/ch/epfl/autograde/auth/provider/ApiKeyAuthenticationProvider.java
@@ -16,7 +16,7 @@ import static java.util.Objects.isNull;
  * @author Hamza REMMAL (hamza.remmal@epfl.ch)
  */
 @Component
-public class ApiKeyAuthenticationProvider implements AuthenticationProvider {
+public final class ApiKeyAuthenticationProvider implements AuthenticationProvider {
 
     /** ??? */
     private final String apiKey;
diff --git a/autograde-service/src/main/java/ch/epfl/autograde/config/AuthenticationConfiguration.java b/autograde-service/src/main/java/ch/epfl/autograde/config/AuthenticationConfiguration.java
new file mode 100644
index 00000000..1dbc9736
--- /dev/null
+++ b/autograde-service/src/main/java/ch/epfl/autograde/config/AuthenticationConfiguration.java
@@ -0,0 +1,23 @@
+package ch.epfl.autograde.config;
+
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationEventPublisher;
+import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
+
+@Configuration
+public class AuthenticationConfiguration {
+
+    /**
+     * ???
+     * @param publisher ???
+     * @return ???
+     * @see <a href="https://docs.spring.io/spring-security/reference/servlet/authentication/events.html">docs.spring.io</a>
+     */
+    @Bean
+    public AuthenticationEventPublisher auth_publisher(ApplicationEventPublisher publisher) {
+        return new DefaultAuthenticationEventPublisher(publisher);
+    }
+
+}
-- 
GitLab