Merge "Remove unnecessary whitespaces before ? during init"
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/Module.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/Module.java
index 037281d..dc41e8b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/Module.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.oauth.sap.SAPIasModule;
 import com.googlesource.gerrit.plugins.oauth.sap.SAPIasOAuthLoginProvider;
 import java.util.List;
 import org.eclipse.jgit.lib.Config;
@@ -58,21 +59,22 @@
                   externalIdFactory, OAuthServiceProviderExternalIdScheme.create(provider)));
     }
 
-    boolean loginProviderBound = bindOAuthLoginProvider(SAPIasOAuthLoginProvider.class);
+    boolean oAuthModuleInstalled =
+        installOAuthModule(SAPIasOAuthLoginProvider.class, new SAPIasModule());
 
-    if (!loginProviderBound) {
+    if (!oAuthModuleInstalled) {
       bind(OAuthLoginProvider.class)
           .annotatedWith(Exports.named(pluginName))
           .to(DisabledOAuthLoginProvider.class);
     }
   }
 
-  private boolean bindOAuthLoginProvider(Class<SAPIasOAuthLoginProvider> loginClass) {
+  private boolean installOAuthModule(
+      Class<? extends OAuthLoginProvider> loginClass, AbstractModule oAuthModule) {
     String loginProviderName = loginClass.getAnnotation(OAuthServiceProviderConfig.class).name();
     String cfgSuffix = OAuthPluginConfigFactory.getConfigSuffix(loginProviderName);
-    String extIdScheme = OAuthServiceProviderExternalIdScheme.create(loginProviderName);
     if (cfg.getString("plugin", pluginName + cfgSuffix, InitOAuth.CLIENT_ID) != null) {
-      bind(OAuthLoginProvider.class).annotatedWith(Exports.named(extIdScheme)).to(loginClass);
+      install(oAuthModule);
       return true;
     }
     return false;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/sap/SAPIasModule.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/sap/SAPIasModule.java
new file mode 100644
index 0000000..11fefe3
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/sap/SAPIasModule.java
@@ -0,0 +1,37 @@
+// Copyright (C) 2025 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.oauth.sap;
+
+import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.extensions.auth.oauth.OAuthLoginProvider;
+import com.google.inject.AbstractModule;
+import com.google.inject.TypeLiteral;
+import com.googlesource.gerrit.plugins.oauth.OAuthServiceProviderExternalIdScheme;
+import com.sap.cloud.security.token.Token;
+import com.sap.cloud.security.token.validation.CombiningValidator;
+
+public class SAPIasModule extends AbstractModule {
+  @Override
+  public void configure() {
+    String extIdScheme =
+        OAuthServiceProviderExternalIdScheme.create(SAPIasOAuthService.PROVIDER_NAME);
+    bind(new TypeLiteral<CombiningValidator<Token>>() {})
+        .toProvider(SAPIasTokenValidatorProvider.class)
+        .asEagerSingleton();
+    bind(OAuthLoginProvider.class)
+        .annotatedWith(Exports.named(extIdScheme))
+        .to(SAPIasOAuthLoginProvider.class);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/sap/SAPIasOAuthLoginProvider.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/sap/SAPIasOAuthLoginProvider.java
index a029cec..138e1e0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/sap/SAPIasOAuthLoginProvider.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/sap/SAPIasOAuthLoginProvider.java
@@ -25,6 +25,7 @@
 import com.google.gerrit.server.account.externalids.ExternalId;
 import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
 import com.google.gerrit.server.account.externalids.ExternalIds;
+import com.google.gerrit.server.config.PluginConfig;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
@@ -56,11 +57,10 @@
       OAuthPluginConfigFactory cfgFactory,
       ExternalIds externalIds,
       ExternalIdKeyFactory externalIdKeyFactory) {
+    PluginConfig cfg = cfgFactory.create(SAPIasOAuthService.PROVIDER_NAME);
     this.service = service;
     this.enableResourceOwnerPasswordFlow =
-        cfgFactory
-            .create(SAPIasOAuthService.PROVIDER_NAME)
-            .getBoolean("enable-resource-owner-password-flow", false);
+        cfg.getBoolean("enable-resource-owner-password-flow", false);
     this.externalIds = externalIds;
     this.externalIdKeyFactory = externalIdKeyFactory;
     this.extIdScheme =
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/sap/SAPIasOAuthService.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/sap/SAPIasOAuthService.java
index a41c500..4aa2b53 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/sap/SAPIasOAuthService.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/sap/SAPIasOAuthService.java
@@ -38,6 +38,9 @@
 import com.googlesource.gerrit.plugins.oauth.OAuthServiceProviderExternalIdScheme;
 import com.sap.cloud.security.json.DefaultJsonObject;
 import com.sap.cloud.security.token.SapIdToken;
+import com.sap.cloud.security.token.Token;
+import com.sap.cloud.security.token.validation.CombiningValidator;
+import com.sap.cloud.security.token.validation.ValidationResult;
 import java.io.IOException;
 import java.net.URI;
 import java.util.concurrent.ExecutionException;
@@ -57,10 +60,13 @@
   private final boolean enablePKCE;
   private final AuthorizationUrlBuilder authorizationUrlBuilder;
   private final String extIdScheme;
+  private final CombiningValidator<Token> tokenValidator;
 
   @Inject
   SAPIasOAuthService(
-      OAuthPluginConfigFactory cfgFactory, @CanonicalWebUrl Provider<String> urlProvider) {
+      OAuthPluginConfigFactory cfgFactory,
+      @CanonicalWebUrl Provider<String> urlProvider,
+      CombiningValidator<Token> tokenValidator) {
     PluginConfig cfg = cfgFactory.create(PROVIDER_NAME);
     String canonicalWebUrl = urlProvider.get();
     rootUrl = cfg.getString(InitOAuth.ROOT_URL);
@@ -78,6 +84,7 @@
             .build(new SAPIasApi(rootUrl));
     authorizationUrlBuilder = service.createAuthorizationUrlBuilder();
     extIdScheme = OAuthServiceProviderExternalIdScheme.create(PROVIDER_NAME);
+    this.tokenValidator = tokenValidator;
   }
 
   @Override
@@ -86,8 +93,14 @@
     return getUserInfo(t);
   }
 
-  public OAuthUserInfo getUserInfo(OAuth2AccessToken token) {
+  public OAuthUserInfo getUserInfo(OAuth2AccessToken token) throws IOException {
     SapIdToken sapToken = new SapIdToken(getIdToken(token));
+    ValidationResult res = tokenValidator.validate(sapToken);
+    if (!res.isValid()) {
+      log.warn("Invalid token received for " + sapToken.getClaimAsString("sub"));
+      throw new IOException("Authentication error");
+    }
+
     String username = sapToken.getClaimAsString("sub");
     String externalId = this.extIdScheme + ":" + username;
     String email = sapToken.getClaimAsString("email");
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/sap/SAPIasTokenValidatorProvider.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/sap/SAPIasTokenValidatorProvider.java
new file mode 100644
index 0000000..2f1c28d
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/sap/SAPIasTokenValidatorProvider.java
@@ -0,0 +1,67 @@
+// Copyright (C) 2025 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.oauth.sap;
+
+import static com.googlesource.gerrit.plugins.oauth.sap.SAPIasOAuthService.PROVIDER_NAME;
+
+import com.google.common.base.Splitter;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.oauth.InitOAuth;
+import com.googlesource.gerrit.plugins.oauth.OAuthPluginConfigFactory;
+import com.sap.cloud.security.client.DefaultHttpClientFactory;
+import com.sap.cloud.security.config.ClientCredentials;
+import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
+import com.sap.cloud.security.config.OAuth2ServiceConfigurationBuilder;
+import com.sap.cloud.security.config.Service;
+import com.sap.cloud.security.token.Token;
+import com.sap.cloud.security.token.validation.CombiningValidator;
+import com.sap.cloud.security.token.validation.validators.JwtValidatorBuilder;
+import java.util.List;
+
+@Singleton
+public class SAPIasTokenValidatorProvider implements Provider<CombiningValidator<Token>> {
+  private static final String ONDEMAND_DOMAIN = ".ondemand.com";
+  private static final String CLOUD_DOMAIN = ".cloud.sap";
+
+  private final PluginConfig cfg;
+  private final OAuth2ServiceConfiguration serviceConfiguration;
+
+  @Inject
+  SAPIasTokenValidatorProvider(OAuthPluginConfigFactory cfgFactory) {
+    cfg = cfgFactory.create(PROVIDER_NAME);
+
+    List<String> rootUrlParts = Splitter.on('.').splitToList(cfg.getString(InitOAuth.ROOT_URL));
+    String universeSubdomain = rootUrlParts.get(rootUrlParts.size() - 3);
+    serviceConfiguration =
+        OAuth2ServiceConfigurationBuilder.forService(Service.IAS)
+            .withUrl(cfg.getString(InitOAuth.ROOT_URL))
+            .withClientId(cfg.getString(InitOAuth.CLIENT_ID))
+            .withDomains(universeSubdomain + ONDEMAND_DOMAIN, universeSubdomain + CLOUD_DOMAIN)
+            .build();
+  }
+
+  @Override
+  public CombiningValidator<Token> get() {
+    ClientCredentials clientCredentials =
+        new ClientCredentials(
+            cfg.getString(InitOAuth.CLIENT_ID), cfg.getString(InitOAuth.CLIENT_SECRET));
+    return JwtValidatorBuilder.getInstance(serviceConfiguration)
+        .withHttpClient(new DefaultHttpClientFactory().createClient(clientCredentials))
+        .build();
+  }
+}