/**
   * Establish the SAML2IDP using the PROXY_URLPATH_CONTEXT from the session<br>
   * Also puts the SAML2IDP's EntityId in the Session as PROXY_SHADOWED_ENTITYID<br>
   * <br>
   * When no SAML2IDP match could be made, there will be no PROXY_SHADOWED_ENTITYID attribute
   * written in the session, and null is returned.
   *
   * @param oAttributes Session Attributes
   * @param oURLPathContext non-null initialized URLPathContext instance to process
   * @return SAML2IDP organization as established, or null when no match could be made
   */
  protected SAML2IDP processURLPathContext(
      ISessionAttributes oAttributes, URLPathContext oURLPathContext) throws OAException {
    // Did we try to find a match before?
    if (oAttributes.contains(
        com.alfaariss.oa.util.session.ProxyAttributes.class,
        com.alfaariss.oa.util.session.ProxyAttributes.PROXY_SHADOWED_IDPID)) {
      String sShadowedEntityId =
          (String)
              oAttributes.get(
                  com.alfaariss.oa.util.session.ProxyAttributes.class,
                  com.alfaariss.oa.util.session.ProxyAttributes.PROXY_SHADOWED_IDPID);

      // Re-use this intelligence, and return the SAML2IDP instance
      IIDP oIDP = _organizationStorage.getIDP(sShadowedEntityId);
      if (oIDP instanceof SAML2IDP) {
        _logger.info("Found IDP '" + sShadowedEntityId + "' from previous URLPath Context match");
        return (SAML2IDP) oIDP;
      } else {
        _logger.warn(
            "Non-SAML2IDP found in IDP Storage - inform developers of this condition! (1)");
        return null;
      }
    }

    String sIValue = oURLPathContext.getParams().get("i");
    if (sIValue == null) {
      _logger.info("No 'i' value found in URLPath Context path ('" + oURLPathContext + "')");
      return null;
    }

    List<IIDP> lAllIDPs = _organizationStorage.getAll();
    for (IIDP oIDP : lAllIDPs) {
      String sIDPEntityIdHash = DigestUtils.shaHex(oIDP.getID());
      if (sIDPEntityIdHash.equalsIgnoreCase(sIValue)) {
        _logger.info("Found IDP '" + oIDP.getID() + "' in matching URLPath Context");
        if (oIDP instanceof SAML2IDP) {
          // Store the IDP that was found on the map
          oAttributes.put(
              com.alfaariss.oa.util.session.ProxyAttributes.class,
              com.alfaariss.oa.util.session.ProxyAttributes.PROXY_SHADOWED_IDPID,
              oIDP.getID());

          // Return result
          return (SAML2IDP) oIDP;
        } else {
          _logger.warn(
              "Non-SAML2IDP found in IDP Storage - inform developers of this condition! (2)");
          return null;
        }
      }
    }

    _logger.info("No IDP found for provided i");
    return null;
  }
  /**
   * Extracts the forced IDP list from the session.
   *
   * @param session The authentication session.
   * @return The forced IDPs.
   * @throws OAException If organization storage exist check can't be performed.
   */
  @SuppressWarnings("unchecked")
  private List<String> getForcedIDPs(ISession session) throws OAException {
    List<String> retval = new Vector<String>();

    IUser oUser = session.getUser();
    if (oUser instanceof SAMLRemoteUser) {
      SAMLRemoteUser remoteUser = (SAMLRemoteUser) oUser;
      String sRemoteIdP = remoteUser.getOrganization();
      if (sRemoteIdP != null && _organizationStorage.exists(sRemoteIdP)) {
        StringBuffer sbDebug = new StringBuffer();
        sbDebug.append("There is a Remote SAML User available in session with ID '");
        sbDebug.append(session.getId());
        sbDebug.append("' that is known at remote IdP '");
        sbDebug.append(sRemoteIdP);
        sbDebug.append("' so this IdP will be forced");
        _logger.debug(sbDebug.toString());
        retval.add(sRemoteIdP);
        return retval;
      }
    }

    ISessionAttributes atts = session.getAttributes();
    String sGetComplete =
        (String) atts.get(ProxyAttributes.class, ProxyAttributes.IDPLIST_GETCOMPLETE);

    if (sGetComplete != null) {
      _logger.debug(
          "Using proxy attribute: " + ProxyAttributes.IDPLIST_GETCOMPLETE + ": " + sGetComplete);
      // getcomplete
      IDPList idpList = null;
      try {
        if (_mRemoteIDPLists.containsKey(sGetComplete)) {
          idpList = _mRemoteIDPLists.get(sGetComplete).getList();
        } else {
          RemoteIDPListEntry entry = new RemoteIDPListEntry(sGetComplete, 1000);
          idpList = entry.getList();

          // DD Add the RemoteIDPListEntry to a map for caching purposes; The getEntry() retrieves
          // the list from the url.
          _mRemoteIDPLists.put(sGetComplete, entry);
        }

        if (idpList != null) {
          for (IDPEntry entry : idpList.getIDPEntrys()) {
            retval.add(entry.getProviderID());
          }
        }
      } catch (ResourceException e) {
        _logger.warn("Failed retrieval of IDPList from GetComplete URL: " + sGetComplete, e);
      }
    }

    List<SAML2IDPEntry> idpList =
        (List<SAML2IDPEntry>) atts.get(ProxyAttributes.class, ProxyAttributes.IDPLIST);
    if (idpList != null) {
      if (_logger.isDebugEnabled()) {
        StringBuffer sbMessage = new StringBuffer("Using proxy attribute ");
        sbMessage.append(ProxyAttributes.IDPLIST);
        sbMessage.append(": ").append(idpList);
        _logger.debug(sbMessage);
      }

      for (SAML2IDPEntry entry : idpList) {
        // DD We currently ignore the proxied SAML2IDPEntry.getName() (friendlyname) and
        // SAML2IDPEntry.getLoc()
        String sID = entry.getProviderID();
        if (sID != null) {
          if (!retval.contains(sID)) retval.add(sID);
        }
      }
    }

    Collection cForcedOrganizations =
        (Collection)
            atts.get(
                com.alfaariss.oa.util.session.ProxyAttributes.class,
                com.alfaariss.oa.util.session.ProxyAttributes.FORCED_ORGANIZATIONS);
    if (cForcedOrganizations != null) {
      if (_logger.isDebugEnabled()) {
        StringBuffer sbMessage = new StringBuffer("Using proxy attribute ");
        sbMessage.append(com.alfaariss.oa.util.session.ProxyAttributes.FORCED_ORGANIZATIONS);
        sbMessage.append(": ").append(cForcedOrganizations);
        _logger.debug(sbMessage);
      }
      for (Object oForceOrganization : cForcedOrganizations) {
        String sForceOrganization = (String) oForceOrganization;
        if (!retval.contains(sForceOrganization)) retval.add(sForceOrganization);
      }
    }

    return retval;
  }
  /**
   * Requestor selection based on RemoteASelectMethod.authenticate.
   *
   * <p>When implementing Synchronous (querying) authentication, this method should be adapted. -
   * Warnings
   *
   * @see
   *     com.alfaariss.oa.sso.authentication.web.IWebAuthenticationMethod#authenticate(javax.servlet.http.HttpServletRequest,
   *     javax.servlet.http.HttpServletResponse, com.alfaariss.oa.api.session.ISession)
   */
  @SuppressWarnings("unchecked")
  public UserEvent authenticate(
      HttpServletRequest request, HttpServletResponse response, ISession session)
      throws OAException {
    try {
      ISessionAttributes oAttributes = session.getAttributes();

      // check proxy att:
      Integer intCnt = (Integer) oAttributes.get(ProxyAttributes.class, ProxyAttributes.PROXYCOUNT);
      if (intCnt != null && intCnt <= 0) {
        _logger.debug("No more authentication proxying allowed: " + intCnt);
        _eventLogger.info(
            new UserEventLogItem(
                session,
                request.getRemoteAddr(),
                UserEvent.AUTHN_METHOD_FAILED,
                this,
                "ProxyCount <= 0"));
        return UserEvent.AUTHN_METHOD_FAILED;
      }

      SAML2IDP organization = null;
      List<Warnings> warnings = null;

      // Figure out whether there exists a pre-selected organization in the URLPath context
      URLPathContext oURLPathContext =
          (URLPathContext)
              oAttributes.get(
                  com.alfaariss.oa.util.session.ProxyAttributes.class,
                  com.alfaariss.oa.util.session.ProxyAttributes.PROXY_URLPATH_CONTEXT);

      if (oURLPathContext != null) {
        organization = processURLPathContext(oAttributes, oURLPathContext);
      }

      if (organization != null) {
        _logger.info("Established organization from URLPathContext: " + organization.getID());

        // Add to Session context if it is not there yet
        if (!oAttributes.contains(
            SAML2AuthenticationMethod.class, _sMethodId + "." + SELECTED_ORGANIZATION)) {
          oAttributes.put(
              SAML2AuthenticationMethod.class, _sMethodId, SELECTED_ORGANIZATION, organization);
        }
      } else {
        if (oAttributes.contains(
            SAML2AuthenticationMethod.class, _sMethodId + "." + SELECTED_ORGANIZATION)) {
          organization =
              (SAML2IDP)
                  oAttributes.get(
                      SAML2AuthenticationMethod.class, _sMethodId + "." + SELECTED_ORGANIZATION);
        } else {
          List<SAML2IDP> listSelectableOrganizations = null;

          if (oAttributes.contains(
              SAML2AuthenticationMethod.class, _sMethodId + "." + LIST_AVAILABLE_ORGANIZATIONS)) {
            // The selected organization was not available, select again:
            listSelectableOrganizations =
                (List<SAML2IDP>)
                    oAttributes.get(
                        SAML2AuthenticationMethod.class,
                        _sMethodId + "." + LIST_AVAILABLE_ORGANIZATIONS);

            warnings = new Vector<Warnings>();
            warnings.add(Warnings.WARNING_ORGANIZATION_UNAVAILABLE);
          } else {
            IUser oUser = session.getUser();
            if (oUser != null) {
              // verify if user that was identified in previous authn method may use this SAML2
              // authn method
              if (!oUser.isAuthenticationRegistered(_sMethodId)) {
                _eventLogger.info(
                    new UserEventLogItem(
                        session,
                        request.getRemoteAddr(),
                        UserEvent.AUTHN_METHOD_NOT_REGISTERED,
                        this,
                        null));

                return UserEvent.AUTHN_METHOD_NOT_REGISTERED;
              }
            }

            listSelectableOrganizations = new Vector<SAML2IDP>();
            Vector fallbackList = new Vector<String>();

            Collection<String> cForcedOrganizations = getForcedIDPs(session);
            if (cForcedOrganizations != null && !cForcedOrganizations.isEmpty())
              oAttributes.put(
                  SAML2AuthNConstants.class,
                  SAML2AuthNConstants.FORCED_ORGANIZATIONS,
                  cForcedOrganizations);

            List<SAML2IDP> listIDPs = _organizationStorage.getAll();
            for (SAML2IDP saml2IDP : listIDPs) {
              fallbackList.add(saml2IDP);
              if (cForcedOrganizations == null || cForcedOrganizations.contains(saml2IDP.getID())) {
                // if no forced organizations are defined or organization is in the forced
                // organization list: Add to select organization list.
                listSelectableOrganizations.add(saml2IDP);
              }
            }

            if (listSelectableOrganizations.isEmpty()) {
              // DD if no forced orgs are known locally, add all and let user decide.
              // Make sure proxy orgs are send with AuthN request
              listSelectableOrganizations = fallbackList;
            }
          }

          if (listSelectableOrganizations.size() == 0) {
            _logger.debug("No organizations available to choose from");
            _eventLogger.info(
                new UserEventLogItem(
                    session,
                    request.getRemoteAddr(),
                    UserEvent.AUTHN_METHOD_NOT_SUPPORTED,
                    this,
                    null));

            return UserEvent.AUTHN_METHOD_NOT_SUPPORTED;
          }

          if (_oSelector == null) {
            organization = listSelectableOrganizations.get(0);

            _logger.debug("No selector configured, using: " + organization.getID());
          } else {
            try {
              // Select requestor
              organization =
                  _oSelector.resolve(
                      request,
                      response,
                      session,
                      listSelectableOrganizations,
                      _sFriendlyName,
                      warnings);
            } catch (OAException e) {
              _eventLogger.info(
                  new UserEventLogItem(
                      session,
                      request.getRemoteAddr(),
                      UserEvent.INTERNAL_ERROR,
                      this,
                      "selecting organization"));
              throw e;
            }
          }

          if (organization == null) {
            // Page is shown
            _eventLogger.info(
                new UserEventLogItem(
                    session,
                    request.getRemoteAddr(),
                    UserEvent.AUTHN_METHOD_IN_PROGRESS,
                    this,
                    null));

            return UserEvent.AUTHN_METHOD_IN_PROGRESS;
          }

          oAttributes.put(
              SAML2AuthenticationMethod.class, _sMethodId, SELECTED_ORGANIZATION, organization);

          listSelectableOrganizations.remove(organization);
          oAttributes.put(
              SAML2AuthenticationMethod.class,
              _sMethodId,
              LIST_AVAILABLE_ORGANIZATIONS,
              listSelectableOrganizations);
        }
      }

      UserEvent event = null;

      if (_profileWebBrowserSSO != null) {
        event =
            _profileWebBrowserSSO.process(
                request, response, session, organization, _htAttributeMapper);

        _eventLogger.info(
            new UserEventLogItem(session, request.getRemoteAddr(), event, this, null));
      } else {
        _eventLogger.info(
            new UserEventLogItem(
                session,
                request.getRemoteAddr(),
                UserEvent.AUTHN_METHOD_FAILED,
                this,
                "No suitable SAML2 profile could be found for authentication"));
        event = UserEvent.AUTHN_METHOD_FAILED;
      }

      if (event == UserEvent.AUTHN_METHOD_FAILED && _bEnableFallback) {
        // fallback
        event = UserEvent.AUTHN_METHOD_IN_PROGRESS;
        oAttributes.remove(
            SAML2AuthenticationMethod.class, _sMethodId + "." + SELECTED_ORGANIZATION);

        _eventLogger.info(
            new UserEventLogItem(
                session,
                request.getRemoteAddr(),
                UserEvent.AUTHN_METHOD_IN_PROGRESS,
                this,
                "Fallback mechanism activated"));

        event = authenticate(request, response, session);
      }

      return event;
    } catch (OAException oae) {
      _eventLogger.info(
          new UserEventLogItem(
              session,
              request.getRemoteAddr(),
              UserEvent.AUTHN_METHOD_FAILED,
              this,
              oae.getLocalizedMessage()));

      throw oae;
    }
  }