/**
   * This is override because of query string values hard coded and input values validations are not
   * required.
   *
   * @param request
   * @param response
   * @param context
   * @throws AuthenticationFailedException
   */
  @Override
  protected void initiateAuthenticationRequest(
      HttpServletRequest request, HttpServletResponse response, AuthenticationContext context)
      throws AuthenticationFailedException {

    try {
      Map<String, String> authenticatorProperties = context.getAuthenticatorProperties();
      if (authenticatorProperties != null) {
        String clientId = authenticatorProperties.get(OIDCAuthenticatorConstants.CLIENT_ID);
        String authorizationEP;
        if (getAuthorizationServerEndpoint(authenticatorProperties) != null) {
          authorizationEP = getAuthorizationServerEndpoint(authenticatorProperties);
        } else {
          authorizationEP =
              authenticatorProperties.get(OIDCAuthenticatorConstants.OAUTH2_AUTHZ_URL);
        }

        String callBackUrl =
            authenticatorProperties.get(GoogleOAuth2AuthenticationConstant.CALLBACK_URL);

        if (log.isDebugEnabled()) {
          log.debug("Google-callback-url : " + callBackUrl);
        }

        if (callBackUrl == null) {
          callBackUrl = CarbonUIUtil.getAdminConsoleURL(request);
          callBackUrl = callBackUrl.replace("commonauth/carbon/", "commonauth");
        }

        String state = context.getContextIdentifier() + "," + OIDCAuthenticatorConstants.LOGIN_TYPE;

        state = getState(state, authenticatorProperties);

        OAuthClientRequest authzRequest;

        // This is the query string need to send in order to get email and
        // profile
        String queryString = GoogleOAuth2AuthenticationConstant.QUERY_STRING;

        authzRequest =
            OAuthClientRequest.authorizationLocation(authorizationEP)
                .setClientId(clientId)
                .setRedirectURI(callBackUrl)
                .setResponseType(OIDCAuthenticatorConstants.OAUTH2_GRANT_TYPE_CODE)
                .setState(state)
                .buildQueryMessage();

        String loginPage = authzRequest.getLocationUri();
        String domain = request.getParameter("domain");

        if (domain != null) {
          loginPage = loginPage + "&fidp=" + domain;
        }

        if (queryString != null) {
          if (!queryString.startsWith("&")) {
            loginPage = loginPage + "&" + queryString;
          } else {
            loginPage = loginPage + queryString;
          }
        }
        response.sendRedirect(loginPage);

      } else {
        if (log.isDebugEnabled()) {
          log.debug("Error while retrieving properties. Authenticator Properties cannot be null");
        }
        throw new AuthenticationFailedException(
            "Error while retrieving properties. Authenticator Properties cannot be null");
      }
    } catch (IOException e) {
      throw new AuthenticationFailedException("Exception while sending to the login page", e);
    } catch (OAuthSystemException e) {
      throw new AuthenticationFailedException(
          "Exception while building authorization code request", e);
    }
  }
  /**
   * this method are overridden for extra claim request to google end-point
   *
   * @param request
   * @param response
   * @param context
   * @throws AuthenticationFailedException
   */
  @Override
  protected void processAuthenticationResponse(
      HttpServletRequest request, HttpServletResponse response, AuthenticationContext context)
      throws AuthenticationFailedException {

    try {

      Map<String, String> authenticatorProperties = context.getAuthenticatorProperties();
      String clientId = authenticatorProperties.get(OIDCAuthenticatorConstants.CLIENT_ID);
      String clientSecret = authenticatorProperties.get(OIDCAuthenticatorConstants.CLIENT_SECRET);
      String tokenEndPoint;
      if (getTokenEndpoint(authenticatorProperties) != null) {
        tokenEndPoint = getTokenEndpoint(authenticatorProperties);
      } else {
        tokenEndPoint = authenticatorProperties.get(OIDCAuthenticatorConstants.OAUTH2_TOKEN_URL);
      }

      String callBackUrl =
          authenticatorProperties.get(GoogleOAuth2AuthenticationConstant.CALLBACK_URL);

      log.debug("callBackUrl : " + callBackUrl);

      if (callBackUrl == null) {
        callBackUrl = CarbonUIUtil.getAdminConsoleURL(request);
        callBackUrl = callBackUrl.replace("commonauth/carbon/", "commonauth");
      }

      @SuppressWarnings({"unchecked"})
      Map<String, String> paramValueMap =
          (Map<String, String>) context.getProperty("oidc:param.map");

      if (paramValueMap != null && paramValueMap.containsKey("redirect_uri")) {
        callBackUrl = paramValueMap.get("redirect_uri");
      }

      OAuthAuthzResponse authzResponse = OAuthAuthzResponse.oauthCodeAuthzResponse(request);
      String code = authzResponse.getCode();

      OAuthClientRequest accessRequest = null;
      accessRequest = getAccessRequest(tokenEndPoint, clientId, clientSecret, callBackUrl, code);

      // create OAuth client that uses custom http client under the hood
      OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
      OAuthClientResponse oAuthResponse = null;
      oAuthResponse = getOAuthResponse(accessRequest, oAuthClient, oAuthResponse);
      // TODO : return access token and id token to framework
      String accessToken = "";
      String idToken = "";
      if (oAuthResponse != null) {
        accessToken = oAuthResponse.getParam(OIDCAuthenticatorConstants.ACCESS_TOKEN);
        idToken = oAuthResponse.getParam(OIDCAuthenticatorConstants.ID_TOKEN);
      }

      if (accessToken != null && (idToken != null || !requiredIDToken(authenticatorProperties))) {

        context.setProperty(OIDCAuthenticatorConstants.ACCESS_TOKEN, accessToken);

        if (idToken != null) {
          context.setProperty(OIDCAuthenticatorConstants.ID_TOKEN, idToken);

          String base64Body = idToken.split("\\.")[1];
          byte[] decoded = Base64.decodeBase64(base64Body.getBytes());
          String json = new String(decoded, Charset.forName("utf-8"));

          if (log.isDebugEnabled()) {
            log.debug("Id token json string : " + json);
          }

          Map<String, Object> jsonObject = JSONUtils.parseJSON(json);

          if (jsonObject != null) {
            Map<ClaimMapping, String> claims = getSubjectAttributes(oAuthResponse);

            String authenticatedUser =
                (String) jsonObject.get(OIDCAuthenticatorConstants.Claim.EMAIL);
            AuthenticatedUser authenticatedUserObj =
                AuthenticatedUser.createFederateAuthenticatedUserFromSubjectIdentifier(
                    authenticatedUser);
            authenticatedUserObj.setUserAttributes(claims);
            context.setSubject(authenticatedUserObj);
          } else {
            if (log.isDebugEnabled()) {
              log.debug("Decoded json object is null");
            }
            throw new AuthenticationFailedException("Decoded json object is null");
          }
        } else {
          if (log.isDebugEnabled()) {
            log.debug("Authentication Failed");
          }
          throw new AuthenticationFailedException("Authentication Failed");
        }

      } else {
        throw new AuthenticationFailedException("Authentication Failed");
      }
    } catch (OAuthProblemException e) {
      throw new AuthenticationFailedException("Error occurred while acquiring access token", e);
    } catch (JSONException e) {
      throw new AuthenticationFailedException("Error occurred while parsing json object", e);
    }
  }
  @Override
  protected void processAuthenticationResponse(
      HttpServletRequest request, HttpServletResponse response, AuthenticationContext context)
      throws AuthenticationFailedException {

    try {

      Map<String, String> authenticatorProperties = context.getAuthenticatorProperties();

      String clientId = authenticatorProperties.get(OIDCAuthenticatorConstants.CLIENT_ID);
      String clientSecret = authenticatorProperties.get(OIDCAuthenticatorConstants.CLIENT_SECRET);
      String tokenEndPoint = getTokenEndpoint(authenticatorProperties);

      if (tokenEndPoint == null) {
        tokenEndPoint = authenticatorProperties.get(OIDCAuthenticatorConstants.OAUTH2_TOKEN_URL);
      }

      String callbackUrl = getCallbackUrl(authenticatorProperties);

      if (StringUtils.isBlank(callbackUrl)) {
        callbackUrl = IdentityUtil.getServerURL(FrameworkConstants.COMMONAUTH, true, true);
      }

      @SuppressWarnings({"unchecked"})
      Map<String, String> paramValueMap =
          (Map<String, String>) context.getProperty("oidc:param.map");

      if (paramValueMap != null && paramValueMap.containsKey("redirect_uri")) {
        callbackUrl = paramValueMap.get("redirect_uri");
      }

      OAuthAuthzResponse authzResponse = OAuthAuthzResponse.oauthCodeAuthzResponse(request);
      String code = authzResponse.getCode();

      OAuthClientRequest accessRequest =
          getaccessRequest(tokenEndPoint, clientId, code, clientSecret, callbackUrl);

      // Create OAuth client that uses custom http client under the hood
      OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
      OAuthClientResponse oAuthResponse = getOauthResponse(oAuthClient, accessRequest);

      // TODO : return access token and id token to framework
      String accessToken = oAuthResponse.getParam(OIDCAuthenticatorConstants.ACCESS_TOKEN);

      if (StringUtils.isBlank(accessToken)) {
        throw new AuthenticationFailedException("Access token is empty or null");
      }

      String idToken = oAuthResponse.getParam(OIDCAuthenticatorConstants.ID_TOKEN);

      if (StringUtils.isBlank(idToken) && requiredIDToken(authenticatorProperties)) {
        throw new AuthenticationFailedException("Id token is required and is missing");
      }

      context.setProperty(OIDCAuthenticatorConstants.ACCESS_TOKEN, accessToken);

      AuthenticatedUser authenticatedUserObj;
      Map<ClaimMapping, String> claims = new HashMap<>();
      Map<String, Object> jsonObject = new HashMap<>();

      if (StringUtils.isNotBlank(idToken)) {

        context.setProperty(OIDCAuthenticatorConstants.ID_TOKEN, idToken);

        String base64Body = idToken.split("\\.")[1];
        byte[] decoded = Base64.decodeBase64(base64Body.getBytes());
        String json = new String(decoded);

        jsonObject = JSONUtils.parseJSON(json);

        if (jsonObject == null) {

          if (log.isDebugEnabled()) {
            log.debug("Decoded json object is null");
          }

          throw new AuthenticationFailedException("Decoded json object is null");
        }

        if (log.isDebugEnabled()
            && IdentityUtil.isTokenLoggable(IdentityConstants.IdentityTokens.USER_ID_TOKEN)) {
          log.debug("Retrieved the User Information:" + jsonObject);
        }

        String authenticatedUser = null;
        String isSubjectInClaimsProp =
            context
                .getAuthenticatorProperties()
                .get(IdentityApplicationConstants.Authenticator.SAML2SSO.IS_USER_ID_IN_CLAIMS);

        if (StringUtils.equalsIgnoreCase("true", isSubjectInClaimsProp)) {

          authenticatedUser = getSubjectFromUserIDClaimURI(context);

          if (authenticatedUser == null && log.isDebugEnabled()) {
            log.debug(
                "Subject claim could not be found amongst subject attributes. "
                    + "Defaulting to the sub attribute in IDToken.");
          }
        }

        if (authenticatedUser == null) {

          authenticatedUser = getAuthenticateUser(context, jsonObject, oAuthResponse);

          if (authenticatedUser == null) {
            throw new AuthenticationFailedException("Cannot find federated User Identifier");
          }
        }

        String attributeSeparator = null;

        try {

          String tenantDomain = context.getTenantDomain();

          if (StringUtils.isBlank(tenantDomain)) {
            tenantDomain = MultitenantConstants.SUPER_TENANT_DOMAIN_NAME;
          }

          int tenantId =
              OpenIDConnectAuthenticatorServiceComponent.getRealmService()
                  .getTenantManager()
                  .getTenantId(tenantDomain);
          UserRealm userRealm =
              OpenIDConnectAuthenticatorServiceComponent.getRealmService()
                  .getTenantUserRealm(tenantId);

          if (userRealm != null) {
            UserStoreManager userStore = (UserStoreManager) userRealm.getUserStoreManager();
            attributeSeparator =
                userStore
                    .getRealmConfiguration()
                    .getUserStoreProperty(IdentityCoreConstants.MULTI_ATTRIBUTE_SEPARATOR);
            if (log.isDebugEnabled()) {
              log.debug(
                  "For the claim mapping: "
                      + attributeSeparator
                      + " is used as the attributeSeparator in tenant: "
                      + tenantDomain);
            }
          }

        } catch (UserStoreException e) {
          throw new AuthenticationFailedException(
              "Error while retrieving multi attribute " + "separator", e);
        }

        for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
          buildClaimMappings(claims, entry, attributeSeparator);
        }

        authenticatedUserObj =
            AuthenticatedUser.createFederateAuthenticatedUserFromSubjectIdentifier(
                authenticatedUser);
      } else {

        if (log.isDebugEnabled()) {
          log.debug("The IdToken is null");
        }

        authenticatedUserObj =
            AuthenticatedUser.createFederateAuthenticatedUserFromSubjectIdentifier(
                getAuthenticateUser(context, jsonObject, oAuthResponse));
      }

      claims.putAll(getSubjectAttributes(oAuthResponse, authenticatorProperties));
      authenticatedUserObj.setUserAttributes(claims);

      context.setSubject(authenticatedUserObj);

    } catch (OAuthProblemException e) {
      throw new AuthenticationFailedException("Authentication process failed", e);
    }
  }
  /** Process the response of the Inwebo end-point */
  @Override
  protected void processAuthenticationResponse(
      HttpServletRequest request, HttpServletResponse response, AuthenticationContext context)
      throws AuthenticationFailedException {
    int waitTime;
    int retryInterval;
    String username = null;

    // Getting the last authenticated local user
    for (Integer stepMap : context.getSequenceConfig().getStepMap().keySet())
      if (context.getSequenceConfig().getStepMap().get(stepMap).getAuthenticatedUser() != null
          && context
                  .getSequenceConfig()
                  .getStepMap()
                  .get(stepMap)
                  .getAuthenticatedAutenticator()
                  .getApplicationAuthenticator()
              instanceof LocalApplicationAuthenticator) {
        username =
            String.valueOf(
                context.getSequenceConfig().getStepMap().get(stepMap).getAuthenticatedUser());
        break;
      }
    if (username != null) {
      UserRealm userRealm = null;
      try {
        String tenantDomain = MultitenantUtils.getTenantDomain(username);
        int tenantId = IdentityTenantUtil.getTenantId(tenantDomain);
        RealmService realmService = IdentityTenantUtil.getRealmService();
        userRealm = (UserRealm) realmService.getTenantUserRealm(tenantId);
        username = MultitenantUtils.getTenantAwareUsername(username);
        if (userRealm != null) {
          userId =
              userRealm
                  .getUserStoreManager()
                  .getUserClaimValue(username, InweboConstants.INWEBO_USERID, null)
                  .toString();
        } else {
          throw new AuthenticationFailedException(
              "Cannot find the user claim for the given userId: " + userId);
        }
      } catch (UserStoreException e) {
        throw new AuthenticationFailedException(
            "Error while getting the user realm" + e.getMessage(), e);
      }
    }
    Map<String, String> authenticatorProperties = context.getAuthenticatorProperties();
    if (authenticatorProperties != null) {
      String serviceId = authenticatorProperties.get(InweboConstants.SERVICE_ID);
      String p12file = authenticatorProperties.get(InweboConstants.INWEBO_P12FILE);
      String p12password = authenticatorProperties.get(InweboConstants.INWEBO_P12PASSWORD);
      if (!StringUtils.isEmpty(authenticatorProperties.get(InweboConstants.RETRY_COUNT))) {
        waitTime = Integer.parseInt(authenticatorProperties.get(InweboConstants.RETRY_COUNT));
      } else {
        waitTime = Integer.parseInt(InweboConstants.WAITTIME_DEFAULT);
      }
      if (!StringUtils.isEmpty(authenticatorProperties.get(InweboConstants.RETRY_INTERVAL))) {
        retryInterval =
            Integer.parseInt(authenticatorProperties.get(InweboConstants.RETRY_INTERVAL));
      } else {
        retryInterval = Integer.parseInt(InweboConstants.RETRYINTERVAL_DEFAULT);
      }
      PushRestCall push =
          new PushRestCall(serviceId, p12file, p12password, userId, waitTime, retryInterval);
      pushResponse = push.run();

      if (pushResponse.contains(InweboConstants.PUSHRESPONSE)) {
        if (log.isDebugEnabled()) {
          log.info("Authentication successful");
        }
        context.setSubject(
            AuthenticatedUser.createLocalAuthenticatedUserFromSubjectIdentifier(userId));
      } else {
        throw new AuthenticationFailedException("Authentication failed");
      }
      pushResponse = null;
      userId = null;
    } else {
      throw new AuthenticationFailedException("Required parameters are empty");
    }
  }
  @Override
  protected void initiateAuthenticationRequest(
      HttpServletRequest request, HttpServletResponse response, AuthenticationContext context)
      throws AuthenticationFailedException {

    try {
      Map<String, String> authenticatorProperties = context.getAuthenticatorProperties();
      if (authenticatorProperties != null) {
        String clientId = authenticatorProperties.get(OIDCAuthenticatorConstants.CLIENT_ID);
        String authorizationEP = getAuthorizationServerEndpoint(authenticatorProperties);

        if (authorizationEP == null) {
          authorizationEP =
              authenticatorProperties.get(OIDCAuthenticatorConstants.OAUTH2_AUTHZ_URL);
        }

        String callbackurl = getCallbackUrl(authenticatorProperties);

        if (StringUtils.isBlank(callbackurl)) {
          callbackurl = IdentityUtil.getServerURL(FrameworkConstants.COMMONAUTH, true, true);
        }

        String state = context.getContextIdentifier() + "," + OIDCAuthenticatorConstants.LOGIN_TYPE;

        state = getState(state, authenticatorProperties);

        OAuthClientRequest authzRequest;

        String queryString = getQueryString(authenticatorProperties);
        Map<String, String> paramValueMap = new HashMap<>();

        if (StringUtils.isNotBlank(queryString)) {
          String[] params = queryString.split("&");
          if (params != null && params.length > 0) {
            for (String param : params) {
              String[] intParam = param.split("=");
              paramValueMap.put(intParam[0], intParam[1]);
            }
            context.setProperty("oidc:param.map", paramValueMap);
          }
        }

        String scope = paramValueMap.get("scope");

        if (scope == null) {
          scope = OIDCAuthenticatorConstants.OAUTH_OIDC_SCOPE;
        }

        scope = getScope(scope, authenticatorProperties);

        if (queryString != null
            && queryString.toLowerCase().contains("scope=")
            && queryString.toLowerCase().contains("redirect_uri=")) {
          authzRequest =
              OAuthClientRequest.authorizationLocation(authorizationEP)
                  .setClientId(clientId)
                  .setResponseType(OIDCAuthenticatorConstants.OAUTH2_GRANT_TYPE_CODE)
                  .setState(state)
                  .buildQueryMessage();
        } else if (queryString != null && queryString.toLowerCase().contains("scope=")) {
          authzRequest =
              OAuthClientRequest.authorizationLocation(authorizationEP)
                  .setClientId(clientId)
                  .setRedirectURI(callbackurl)
                  .setResponseType(OIDCAuthenticatorConstants.OAUTH2_GRANT_TYPE_CODE)
                  .setState(state)
                  .buildQueryMessage();
        } else if (queryString != null && queryString.toLowerCase().contains("redirect_uri=")) {
          authzRequest =
              OAuthClientRequest.authorizationLocation(authorizationEP)
                  .setClientId(clientId)
                  .setResponseType(OIDCAuthenticatorConstants.OAUTH2_GRANT_TYPE_CODE)
                  .setScope(OIDCAuthenticatorConstants.OAUTH_OIDC_SCOPE)
                  .setState(state)
                  .buildQueryMessage();

        } else {
          authzRequest =
              OAuthClientRequest.authorizationLocation(authorizationEP)
                  .setClientId(clientId)
                  .setRedirectURI(callbackurl)
                  .setResponseType(OIDCAuthenticatorConstants.OAUTH2_GRANT_TYPE_CODE)
                  .setScope(scope)
                  .setState(state)
                  .buildQueryMessage();
        }

        String loginPage = authzRequest.getLocationUri();
        String domain = request.getParameter("domain");

        if (domain != null) {
          loginPage = loginPage + "&fidp=" + domain;
        }

        if (queryString != null) {
          if (!queryString.startsWith("&")) {
            loginPage = loginPage + "&" + queryString;
          } else {
            loginPage = loginPage + queryString;
          }
        }
        response.sendRedirect(loginPage);
      } else {
        if (log.isDebugEnabled()) {
          log.debug("Error while retrieving properties. Authenticator Properties cannot be null");
        }
        throw new AuthenticationFailedException(
            "Error while retrieving properties. Authenticator Properties cannot be null");
      }
    } catch (IOException e) {
      log.error("Exception while sending to the login page", e);
      throw new AuthenticationFailedException(e.getMessage(), e);
    } catch (OAuthSystemException e) {
      log.error("Exception while building authorization code request", e);
      throw new AuthenticationFailedException(e.getMessage(), e);
    }
    return;
  }