From 8a96cab07cdffd754fb43a91dc1e17033703be77 Mon Sep 17 00:00:00 2001
From: Sam <128482925+samuelcostae@users.noreply.github.com>
Date: Fri, 18 Aug 2023 14:43:07 +0100
Subject: [PATCH] Feature/extensions bwc setting (#3180)

### Description
This Draft PR includes the new setting bwcPluginMode (backward
compatible plugin mode for extensions )

### Issues Resolved
#2616

Is this a backport? If so, please add backport PR # and/or commits #

### Testing
[Please provide details of testing done: unit testing, integration
testing and manual testing]

### Check List
- [ ] New functionality includes testing
- [ ] New functionality has been documented
- [x] Commits are signed per the DCO using --signoff

By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and
signing off your commits, please check
[here](https://github.com/opensearch-project/OpenSearch/blob/main/CONTRIBUTING.md#developer-certificate-of-origin).

---------

Signed-off-by: Sam <samuel.costa@eliatra.com>
---
 .../security/OpenSearchSecurityPlugin.java    | 28 +++++++++++-
 .../security/authtoken/jwt/JwtVendor.java     | 13 +++++-
 .../security/support/ConfigConstants.java     |  5 +++
 .../security/authtoken/jwt/JwtVendorTest.java | 45 ++++++++++++++++++-
 4 files changed, 88 insertions(+), 3 deletions(-)

diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
index 180fab8f68..b40b0186ac 100644
--- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
+++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
@@ -106,6 +106,7 @@
 import org.opensearch.indices.IndicesService;
 import org.opensearch.indices.SystemIndexDescriptor;
 import org.opensearch.plugins.ClusterPlugin;
+import org.opensearch.plugins.ExtensionAwarePlugin;
 import org.opensearch.plugins.MapperPlugin;
 import org.opensearch.repositories.RepositoriesService;
 import org.opensearch.rest.RestController;
@@ -194,7 +195,15 @@
 import org.opensearch.watcher.ResourceWatcherService;
 // CS-ENFORCE-SINGLE
 
-public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin implements ClusterPlugin, MapperPlugin {
+public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin
+    implements
+        ClusterPlugin,
+        MapperPlugin,
+        // CS-SUPPRESS-SINGLE: RegexpSingleline get Extensions Settings
+        ExtensionAwarePlugin
+// CS-ENFORCE-SINGLE
+
+{
 
     private static final String KEYWORD = ".keyword";
     private static final Logger actionTrace = LogManager.getLogger("opendistro_security_action_trace");
@@ -1110,6 +1119,23 @@ public Settings additionalSettings() {
         }
         return builder.build();
     }
+    // CS-SUPPRESS-SINGLE: RegexpSingleline get Extensions Settings
+
+    @Override
+    public List<Setting<?>> getExtensionSettings() {
+        List<Setting<?>> extensionSettings = new ArrayList<Setting<?>>();
+
+        extensionSettings.add(
+            Setting.boolSetting(
+                ConfigConstants.EXTENSIONS_BWC_PLUGIN_MODE,
+                ConfigConstants.EXTENSIONS_BWC_PLUGIN_MODE_DEFAULT,
+                Property.ExtensionScope,
+                Property.Final
+            )
+        );
+        return extensionSettings;
+    }
+    // CS-ENFORCE-SINGLE:
 
     @Override
     public List<Setting<?>> getSettings() {
diff --git a/src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java b/src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java
index 21d7caad57..4372e2dfee 100644
--- a/src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java
+++ b/src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java
@@ -30,6 +30,7 @@
 import org.apache.logging.log4j.Logger;
 
 import org.opensearch.common.settings.Settings;
+import org.opensearch.security.support.ConfigConstants;
 
 public class JwtVendor {
     private static final Logger logger = LogManager.getLogger(JwtVendor.class);
@@ -40,6 +41,7 @@ public class JwtVendor {
     private final JsonWebKey signingKey;
     private final JoseJwtProducer jwtProducer;
     private final LongSupplier timeProvider;
+    private final Boolean bwcModeEnabled;
 
     public JwtVendor(final Settings settings, final Optional<LongSupplier> timeProvider) {
         JoseJwtProducer jwtProducer = new JoseJwtProducer();
@@ -59,6 +61,12 @@ public JwtVendor(final Settings settings, final Optional<LongSupplier> timeProvi
         } else {
             this.timeProvider = () -> System.currentTimeMillis() / 1000;
         }
+        // CS-SUPPRESS-SINGLE: RegexpSingleline get Extensions Settings
+        this.bwcModeEnabled = settings.getAsBoolean(
+            ConfigConstants.EXTENSIONS_BWC_PLUGIN_MODE,
+            ConfigConstants.EXTENSIONS_BWC_PLUGIN_MODE_DEFAULT
+        );
+        // CS-ENFORCE-SINGLE
     }
 
     /*
@@ -142,7 +150,10 @@ public String createJwt(
             throw new Exception("Roles cannot be null");
         }
 
-        /* TODO: If the backendRoles is not null and the BWC Mode is on, put them into the "dbr" claim */
+        if (bwcModeEnabled && backendRoles != null) {
+            String listOfBackendRoles = String.join(",", backendRoles);
+            jwtClaims.setProperty("dbr", listOfBackendRoles);
+        }
 
         String encodedJwt = jwtProducer.processJwt(jwt);
 
diff --git a/src/main/java/org/opensearch/security/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java
index ee04ff62f3..3b0b6a1091 100644
--- a/src/main/java/org/opensearch/security/support/ConfigConstants.java
+++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java
@@ -320,6 +320,11 @@ public enum RolesMappingResolution {
     public static final String TENANCY_GLOBAL_TENANT_NAME = "global";
     public static final String TENANCY_GLOBAL_TENANT_DEFAULT_NAME = "";
 
+    // CS-SUPPRESS-SINGLE: RegexpSingleline get Extensions Settings
+    public static final String EXTENSIONS_BWC_PLUGIN_MODE = "bwcPluginMode";
+    public static final boolean EXTENSIONS_BWC_PLUGIN_MODE_DEFAULT = false;
+    // CS-ENFORCE-SINGLE
+
     public static Set<String> getSettingAsSet(
         final Settings settings,
         final String key,
diff --git a/src/test/java/org/opensearch/security/authtoken/jwt/JwtVendorTest.java b/src/test/java/org/opensearch/security/authtoken/jwt/JwtVendorTest.java
index 60ed365285..3477432462 100644
--- a/src/test/java/org/opensearch/security/authtoken/jwt/JwtVendorTest.java
+++ b/src/test/java/org/opensearch/security/authtoken/jwt/JwtVendorTest.java
@@ -23,6 +23,7 @@
 import org.junit.Test;
 
 import org.opensearch.common.settings.Settings;
+import org.opensearch.security.support.ConfigConstants;
 
 public class JwtVendorTest {
 
@@ -48,7 +49,7 @@ public void testCreateJwtWithRoles() throws Exception {
         String subject = "admin";
         String audience = "audience_0";
         List<String> roles = List.of("IT", "HR");
-        List<String> backendRoles = List.of("Sales");
+        List<String> backendRoles = List.of("Sales", "Support");
         String expectedRoles = "IT,HR";
         Integer expirySeconds = 300;
         LongSupplier currentTime = () -> (int) 100;
@@ -71,6 +72,48 @@ public void testCreateJwtWithRoles() throws Exception {
         Assert.assertEquals(expectedExp, jwt.getClaim("exp"));
         Assert.assertNotEquals(expectedRoles, jwt.getClaim("er"));
         Assert.assertEquals(expectedRoles, EncryptionDecryptionUtil.decrypt(claimsEncryptionKey, jwt.getClaim("er").toString()));
+        Assert.assertNull(jwt.getClaim("dbr"));
+    }
+
+    @Test
+    public void testCreateJwtWithBackwardsCompatibilityMode() throws Exception {
+        String issuer = "cluster_0";
+        String subject = "admin";
+        String audience = "audience_0";
+        List<String> roles = List.of("IT", "HR");
+        List<String> backendRoles = List.of("Sales", "Support");
+        String expectedRoles = "IT,HR";
+        String expectedBackendRoles = "Sales,Support";
+
+        Integer expirySeconds = 300;
+        LongSupplier currentTime = () -> (int) 100;
+        String claimsEncryptionKey = RandomStringUtils.randomAlphanumeric(16);
+        Settings settings = Settings.builder()
+            .put("signing_key", "abc123")
+            .put("encryption_key", claimsEncryptionKey)
+            // CS-SUPPRESS-SINGLE: RegexpSingleline get Extensions Settings
+            .put(ConfigConstants.EXTENSIONS_BWC_PLUGIN_MODE, true)
+            // CS-ENFORCE-SINGLE
+            .build();
+        Long expectedExp = currentTime.getAsLong() + expirySeconds;
+
+        JwtVendor jwtVendor = new JwtVendor(settings, Optional.of(currentTime));
+        String encodedJwt = jwtVendor.createJwt(issuer, subject, audience, expirySeconds, roles, backendRoles);
+
+        JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer(encodedJwt);
+        JwtToken jwt = jwtConsumer.getJwtToken();
+
+        Assert.assertEquals("obo", jwt.getClaim("typ"));
+        Assert.assertEquals("cluster_0", jwt.getClaim("iss"));
+        Assert.assertEquals("admin", jwt.getClaim("sub"));
+        Assert.assertEquals("audience_0", jwt.getClaim("aud"));
+        Assert.assertNotNull(jwt.getClaim("iat"));
+        Assert.assertNotNull(jwt.getClaim("exp"));
+        Assert.assertEquals(expectedExp, jwt.getClaim("exp"));
+        Assert.assertNotEquals(expectedRoles, jwt.getClaim("er"));
+        Assert.assertEquals(expectedRoles, EncryptionDecryptionUtil.decrypt(claimsEncryptionKey, jwt.getClaim("er").toString()));
+        Assert.assertNotNull(jwt.getClaim("dbr"));
+        Assert.assertEquals(expectedBackendRoles, jwt.getClaim("dbr"));
     }
 
     @Test(expected = Exception.class)