package com.airlock.iam.login.app.misc.configuration.oneshot;

import com.airlock.iam.core.application.configuration.waf.AirlockGatewayConfig;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.login.app.misc.oneshot.impl.MsOfbaOneShotAuthenticationWorkflow;
import com.airlock.iam.login.app.misc.session.UserSpecificRoleTimeoutCalculator;
import com.airlock.iam.plugin.api.annotation.config.Plugin;
import com.airlock.iam.plugin.api.annotation.property.RegexProperty;
import com.airlock.iam.plugin.api.annotation.property.StringProperty;
import com.airlock.iam.plugin.api.annotation.property.RegexProperty.CaseSensitivitySelector;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.regex.Pattern;

@Plugin(
   description = "<b>Microsoft Office Forms Based Authentication (MS-OFBA) One-Shot Target Application</b><br/><b>Note:</b> This target application allows to authenticate HTTP requests based on the original HTTP request sent by the client to Airlock WAF. Therefore, this plugin only works with Airlock Gateway (WAF) Settings being configured.<p><b>MS-OFBA Protocol</b><br/>The MS-OFBA protocol provides a mechanism by which Microsoft Office clients (e.g. Word, Excel, ...) can establish an authenticated session with a server or gateway like Airlock WAF. The three steps for establishing an identity using forms based authentication between a protocol client and a protocol server are as follows:<ol><li><b>Initialization:</b> Prior to opening the document on the remote server, the client sends a so called protocol discovery request, which is a HTTP OPTIONS request allowing the server to determine whether the client is a browser or not, based on the headers sent (see Browser vs. Nonbrowser Clients below). In the case of an nonbrowser client, this target application responds that its authentication method is forms based authentication, by returning a 403 HTTP response including a X-FORMS_BASED_AUTH_REQUIRED header with a URL, pointing to the location to which the client should navigate to authenticate. The response also includes a X-FORMS_BASED_AUTH_RETURN_URL header, which is the location to which the protocol server will redirect the user after a successful authentication.</li><li><b>Negotiation:</b> Having determined that the protocol server is capable of establishing an identity by using forms based authentication, the protocol client renders the HTML returned from the request to the remote location provided by the server in step 1 (X-FORMS_BASED_AUTH_REQUIRED header). Note that the duration of this step is neither deterministic nor specified by this protocol. The reason is that the client will continue to follow as many redirects and refreshes as necessary to successfully establish the identity, until the server redirects to the return URI provided by the server in step 1 (X-FORMS_BASED_AUTH_RETURN_URL header)</li><li><b>Finalization:</b> After the protocol server redirects the protocol client to the return URI, the protocol client assumes that the identity has been successfully established and reissues the original request from step 1. Note that the process for actually establishing the user's identity is not specified by this protocol.</li></ol></p><p><b>Browser vs. Nonbrowser Clients</b><br/>If the request from the client contains a X-FORMS_BASED_AUTH_ACCEPTED HTTP header or the User-Agent header matches the configured user agent regular expression, the client is considered to be a nonbrowser client. In this case, a forms based authentication required response is returned as described in the Initialization step of the protocol. <br/>In the other case, where the client is considered to be a browser, a HTTP 302 redirect to the configured redirect url is returned. In addition, a location parameter is added to the redirect location, pointing to the initially accessed URL on the WAF. Therefore, the effect of accessing this target application with a browser is the same as if the Authentication Flow on the WAF mapping would have been set to Redirect instead of One-Shot.</p>",
   displayName = "MS-OFBA One-Shot Target Application"
)
public class MsOfbaOneShotTargetApplication implements OneShotTargetApplication {
   private static final ELogger LOG = ELoggerFactory.getLogger(MsOfbaOneShotTargetApplication.class);
   @RegexProperty(
      displayName = "URL Pattern",
      description = "The URL pattern (regular expression pattern) to identify this target application.<p>The first pattern (in the list of target applications) that matches the forward URL is used.<br/>The matching is case-insensitive.</p><p>The URL pattern is <b>ignored for the default target application</b>.</p>",
      optional = false,
      exampleValues = {".*", "https://host:8080/app/.*"},
      caseSensitivity = CaseSensitivitySelector.CASE_INSENSITIVE
   )
   private Pattern urlPattern;
   @RegexProperty(
      displayName = "User Agent HTTP Header Pattern",
      description = "To be recognized as a nonbrowser client that supports the MS-OFBA protocol, the protocol client MUST specify either a X-FORMS_BASED_AUTH_ACCEPTED header or a user agent string in an HTTP OPTIONS request. <p>IAM responds with a Forms Based Authentication Required response, as specified in the plugin description, iff <ul><li>The X-FORMS_BASED_AUTH_ACCEPTED header field is present with a value of \"t\" or \"f\" (otherwise it is ignored) or </li> <li>The request contains a User-Agent HTTP header that matches this regular expression pattern.</li></ul>",
      optional = true,
      defaultValue = "Microsoft Office(.*)",
      caseSensitivity = CaseSensitivitySelector.CASE_INSENSITIVE
   )
   private Pattern userAgentPattern;
   @StringProperty(
      displayName = "Browser Redirect URL",
      description = "In case the client is considered to be a browser, the MS-OFBA One-Shot Target Application redirects the client to this URL. In addition, a location parameter, which corresponds to the initially accessed URL on the WAF, is added to the redirect location. Therefore, the effect of accessing this target application with a browser is the same as if the Authentication Flow on the WAF mapping would have been set to Redirect instead of One-Shot.",
      optional = true,
      defaultValue = "/auth/check-login",
      exampleValues = {"/auth/check-login", "https://myhost.com/iamPath/check-login"}
   )
   private String redirectUrl;
   @StringProperty(
      displayName = "MS-OFBA Authentication URL",
      description = "The URL which will be returned to the client as X-FORMS_BASED_AUTH_REQUIRED HTTP header value in the MS-OFBA response. It MUST point to an HTTP-based server. A Location URL parameter, that points to the configured MS-OFBA Success URL will be added to this URL automatically. Therefore, the final MS-OFBA Authentication URL as received by the client wil look similar to https://myhost.com/iamPath/check-login?Location=https%3A%2F%2Fmyhost.com%2Fauth%2Fsuccess.",
      optional = false,
      exampleValues = "https://myhost.com/iamPath/check-login"
   )
   private String msofbaAuthUrl;
   @StringProperty(
      displayName = "MS-OFBA Success URL",
      description = "The URL which will be returned to the client as X-FORMS_BASED_AUTH_RETURN_URL HTTP header value in the MS-OFBA response. It MUST point to an HTTP-based server and accessing the URL has to result in a HTTP 200 response. For this, the IAM Success Servlet, deployed under /&ltiam-deployment-path&gt/msofba-success can be used.",
      optional = false,
      exampleValues = "https://myhost.com/auth/msofba-success"
   )
   private String msofbaSuccessUrl;
   @StringProperty(
      displayName = "MS-OFBA Display Size",
      description = "Optional value of the X-FORMS_BASED_AUTH_DIALOG_SIZE HTTP header as returned to the client in the MS-OFBA response. This value determines the size of the window which is opened by the client when accessing the MS-OFBA Authentication URL. It must follow the format &ltwidth in pixels&gtx&ltheight in pixels&gt. If the size of the dialog box is not specified, the value \"660x495\" is used by the protocol client.",
      optional = true,
      exampleValues = "800x600"
   )
   private String msofbaDialogSize;
   @StringProperty(
      description = "The name of the request parameter telling the authentication application what page the user requested when he/she was redirected to the authentication application.",
      optional = true,
      defaultValue = "Location"
   )
   private String locationParameterName;

   public MsOfbaOneShotAuthenticationWorkflow createWorkflow(
      AirlockGatewayConfig wafConfig, ConfigurationContext context, UserSpecificRoleTimeoutCalculator userSpecificRoleTimeouts
   ) {
      MsOfbaOneShotTargetApplication.MsOfbaConfig msofbaConfig = MsOfbaOneShotTargetApplication.MsOfbaConfig.builder()
         .userAgentMatcher(this.userAgentPattern.asMatchPredicate())
         .authUrl(this.msofbaAuthUrl)
         .successUrl(this.msofbaSuccessUrl)
         .dialogSize(Optional.ofNullable(this.msofbaDialogSize))
         .build();
      return new MsOfbaOneShotAuthenticationWorkflow(wafConfig, msofbaConfig, this.redirectUrl, this.locationParameterName);
   }

   @Override
   public Pattern getUrlPattern() {
      return this.urlPattern;
   }

   public Pattern getUserAgentPattern() {
      return this.userAgentPattern;
   }

   public String getRedirectUrl() {
      return this.redirectUrl;
   }

   public String getMsofbaAuthUrl() {
      return this.msofbaAuthUrl;
   }

   public String getMsofbaSuccessUrl() {
      return this.msofbaSuccessUrl;
   }

   public String getMsofbaDialogSize() {
      return this.msofbaDialogSize;
   }

   public String getLocationParameterName() {
      return this.locationParameterName;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static MsOfbaOneShotTargetApplication.MsOfbaOneShotTargetApplicationBuilder builder() {
      return new MsOfbaOneShotTargetApplication.MsOfbaOneShotTargetApplicationBuilder();
   }

   @SuppressFBWarnings(justification = "generated code")
   public MsOfbaOneShotTargetApplication() {
   }

   @SuppressFBWarnings(justification = "generated code")
   public MsOfbaOneShotTargetApplication(
      Pattern urlPattern,
      Pattern userAgentPattern,
      String redirectUrl,
      String msofbaAuthUrl,
      String msofbaSuccessUrl,
      String msofbaDialogSize,
      String locationParameterName
   ) {
      this.urlPattern = urlPattern;
      this.userAgentPattern = userAgentPattern;
      this.redirectUrl = redirectUrl;
      this.msofbaAuthUrl = msofbaAuthUrl;
      this.msofbaSuccessUrl = msofbaSuccessUrl;
      this.msofbaDialogSize = msofbaDialogSize;
      this.locationParameterName = locationParameterName;
   }

   public static final class MsOfbaConfig {
      private final Predicate<String> userAgentMatcher;
      private final String authUrl;
      private final String successUrl;
      private final Optional<String> dialogSize;

      @SuppressFBWarnings(justification = "generated code")
      MsOfbaConfig(Predicate<String> userAgentMatcher, String authUrl, String successUrl, Optional<String> dialogSize) {
         this.userAgentMatcher = userAgentMatcher;
         this.authUrl = authUrl;
         this.successUrl = successUrl;
         this.dialogSize = dialogSize;
      }

      @SuppressFBWarnings(justification = "generated code")
      public static MsOfbaOneShotTargetApplication.MsOfbaConfig.MsOfbaConfigBuilder builder() {
         return new MsOfbaOneShotTargetApplication.MsOfbaConfig.MsOfbaConfigBuilder();
      }

      @SuppressFBWarnings(justification = "generated code")
      public Predicate<String> getUserAgentMatcher() {
         return this.userAgentMatcher;
      }

      @SuppressFBWarnings(justification = "generated code")
      public String getAuthUrl() {
         return this.authUrl;
      }

      @SuppressFBWarnings(justification = "generated code")
      public String getSuccessUrl() {
         return this.successUrl;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Optional<String> getDialogSize() {
         return this.dialogSize;
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public boolean equals(Object o) {
         if (o == this) {
            return true;
         } else if (!(o instanceof MsOfbaOneShotTargetApplication.MsOfbaConfig other)) {
            return false;
         } else {
            Object this$userAgentMatcher = this.userAgentMatcher;
            Object other$userAgentMatcher = other.userAgentMatcher;
            if (this$userAgentMatcher == null ? other$userAgentMatcher == null : this$userAgentMatcher.equals(other$userAgentMatcher)) {
               Object this$authUrl = this.authUrl;
               Object other$authUrl = other.authUrl;
               if (this$authUrl == null ? other$authUrl == null : this$authUrl.equals(other$authUrl)) {
                  Object this$successUrl = this.successUrl;
                  Object other$successUrl = other.successUrl;
                  if (this$successUrl == null ? other$successUrl == null : this$successUrl.equals(other$successUrl)) {
                     Object this$dialogSize = this.dialogSize;
                     Object other$dialogSize = other.dialogSize;
                     return this$dialogSize == null ? other$dialogSize == null : this$dialogSize.equals(other$dialogSize);
                  } else {
                     return false;
                  }
               } else {
                  return false;
               }
            } else {
               return false;
            }
         }
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public int hashCode() {
         int PRIME = 59;
         int result = 1;
         Object $userAgentMatcher = this.userAgentMatcher;
         result = result * 59 + ($userAgentMatcher == null ? 43 : $userAgentMatcher.hashCode());
         Object $authUrl = this.authUrl;
         result = result * 59 + ($authUrl == null ? 43 : $authUrl.hashCode());
         Object $successUrl = this.successUrl;
         result = result * 59 + ($successUrl == null ? 43 : $successUrl.hashCode());
         Object $dialogSize = this.dialogSize;
         return result * 59 + ($dialogSize == null ? 43 : $dialogSize.hashCode());
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "MsOfbaOneShotTargetApplication.MsOfbaConfig(userAgentMatcher="
            + this.getUserAgentMatcher()
            + ", authUrl="
            + this.getAuthUrl()
            + ", successUrl="
            + this.getSuccessUrl()
            + ", dialogSize="
            + this.getDialogSize()
            + ")";
      }

      @SuppressFBWarnings(justification = "generated code")
      public static class MsOfbaConfigBuilder {
         @SuppressFBWarnings(justification = "generated code")
         private Predicate<String> userAgentMatcher;
         @SuppressFBWarnings(justification = "generated code")
         private String authUrl;
         @SuppressFBWarnings(justification = "generated code")
         private String successUrl;
         @SuppressFBWarnings(justification = "generated code")
         private Optional<String> dialogSize;

         @SuppressFBWarnings(justification = "generated code")
         MsOfbaConfigBuilder() {
         }

         @SuppressFBWarnings(justification = "generated code")
         public MsOfbaOneShotTargetApplication.MsOfbaConfig.MsOfbaConfigBuilder userAgentMatcher(Predicate<String> userAgentMatcher) {
            this.userAgentMatcher = userAgentMatcher;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MsOfbaOneShotTargetApplication.MsOfbaConfig.MsOfbaConfigBuilder authUrl(String authUrl) {
            this.authUrl = authUrl;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MsOfbaOneShotTargetApplication.MsOfbaConfig.MsOfbaConfigBuilder successUrl(String successUrl) {
            this.successUrl = successUrl;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MsOfbaOneShotTargetApplication.MsOfbaConfig.MsOfbaConfigBuilder dialogSize(Optional<String> dialogSize) {
            this.dialogSize = dialogSize;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public MsOfbaOneShotTargetApplication.MsOfbaConfig build() {
            return new MsOfbaOneShotTargetApplication.MsOfbaConfig(this.userAgentMatcher, this.authUrl, this.successUrl, this.dialogSize);
         }

         @SuppressFBWarnings(justification = "generated code")
         @Override
         public String toString() {
            return "MsOfbaOneShotTargetApplication.MsOfbaConfig.MsOfbaConfigBuilder(userAgentMatcher="
               + this.userAgentMatcher
               + ", authUrl="
               + this.authUrl
               + ", successUrl="
               + this.successUrl
               + ", dialogSize="
               + this.dialogSize
               + ")";
         }
      }
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class MsOfbaOneShotTargetApplicationBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Pattern urlPattern;
      @SuppressFBWarnings(justification = "generated code")
      private Pattern userAgentPattern;
      @SuppressFBWarnings(justification = "generated code")
      private String redirectUrl;
      @SuppressFBWarnings(justification = "generated code")
      private String msofbaAuthUrl;
      @SuppressFBWarnings(justification = "generated code")
      private String msofbaSuccessUrl;
      @SuppressFBWarnings(justification = "generated code")
      private String msofbaDialogSize;
      @SuppressFBWarnings(justification = "generated code")
      private String locationParameterName;

      @SuppressFBWarnings(justification = "generated code")
      MsOfbaOneShotTargetApplicationBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public MsOfbaOneShotTargetApplication.MsOfbaOneShotTargetApplicationBuilder urlPattern(Pattern urlPattern) {
         this.urlPattern = urlPattern;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MsOfbaOneShotTargetApplication.MsOfbaOneShotTargetApplicationBuilder userAgentPattern(Pattern userAgentPattern) {
         this.userAgentPattern = userAgentPattern;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MsOfbaOneShotTargetApplication.MsOfbaOneShotTargetApplicationBuilder redirectUrl(String redirectUrl) {
         this.redirectUrl = redirectUrl;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MsOfbaOneShotTargetApplication.MsOfbaOneShotTargetApplicationBuilder msofbaAuthUrl(String msofbaAuthUrl) {
         this.msofbaAuthUrl = msofbaAuthUrl;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MsOfbaOneShotTargetApplication.MsOfbaOneShotTargetApplicationBuilder msofbaSuccessUrl(String msofbaSuccessUrl) {
         this.msofbaSuccessUrl = msofbaSuccessUrl;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MsOfbaOneShotTargetApplication.MsOfbaOneShotTargetApplicationBuilder msofbaDialogSize(String msofbaDialogSize) {
         this.msofbaDialogSize = msofbaDialogSize;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MsOfbaOneShotTargetApplication.MsOfbaOneShotTargetApplicationBuilder locationParameterName(String locationParameterName) {
         this.locationParameterName = locationParameterName;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MsOfbaOneShotTargetApplication build() {
         return new MsOfbaOneShotTargetApplication(
            this.urlPattern,
            this.userAgentPattern,
            this.redirectUrl,
            this.msofbaAuthUrl,
            this.msofbaSuccessUrl,
            this.msofbaDialogSize,
            this.locationParameterName
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "MsOfbaOneShotTargetApplication.MsOfbaOneShotTargetApplicationBuilder(urlPattern="
            + this.urlPattern
            + ", userAgentPattern="
            + this.userAgentPattern
            + ", redirectUrl="
            + this.redirectUrl
            + ", msofbaAuthUrl="
            + this.msofbaAuthUrl
            + ", msofbaSuccessUrl="
            + this.msofbaSuccessUrl
            + ", msofbaDialogSize="
            + this.msofbaDialogSize
            + ", locationParameterName="
            + this.locationParameterName
            + ")";
      }
   }
}
