text: /*
   * JBoss, Home of Professional Open Source.
   * Copyright 2012, Red Hat, Inc., and individual contributors
   * as indicated by the @author tags. See the copyright.txt file in the
   * distribution for a full listing of individual contributors.
   *
   * This is free software; you can redistribute it and/or modify it
   * under the terms of the GNU Lesser General Public License as
   * published by the Free Software Foundation; either version 2.1 of
   * the License, or (at your option) any later version.
   *
   * This software is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this software; if not, write to the Free
   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
   */
  package org.wildfly.extension.picketlink.federation.service;
  
  import org.jboss.as.controller.OperationContext;
  import org.jboss.as.server.deployment.DeploymentUnit;
  import org.jboss.as.web.common.WarMetaData;
  import org.jboss.msc.service.ServiceController;
  import org.jboss.msc.service.ServiceRegistry;
  import org.jboss.msc.service.StartContext;
  import org.jboss.msc.service.StartException;
  import org.jboss.msc.service.StopContext;
  import org.jboss.msc.value.InjectedValue;
  import org.picketlink.common.constants.GeneralConstants;
  import org.picketlink.common.constants.JBossSAMLURIConstants;
  import org.picketlink.common.exceptions.ConfigurationException;
  import org.picketlink.config.federation.KeyValueType;
  import org.picketlink.config.federation.PicketLinkType;
  import org.picketlink.config.federation.ProviderType;
  import org.picketlink.config.federation.STSType;
  import org.picketlink.config.federation.TokenProviderType;
  import org.picketlink.config.federation.handler.Handler;
  import org.picketlink.config.federation.handler.Handlers;
  import org.picketlink.config.federation.parsers.STSConfigParser;
  import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2Handler;
  import org.picketlink.identity.federation.web.handlers.saml2.RolesGenerationHandler;
  import org.picketlink.identity.federation.web.handlers.saml2.SAML2AuthenticationHandler;
  import org.picketlink.identity.federation.web.handlers.saml2.SAML2EncryptionHandler;
  import org.picketlink.identity.federation.web.handlers.saml2.SAML2LogOutHandler;
  import org.picketlink.identity.federation.web.handlers.saml2.SAML2SignatureValidationHandler;
  import org.wildfly.extension.picketlink.federation.config.ProviderConfiguration;
  import org.wildfly.extension.picketlink.federation.metrics.PicketLinkSubsystemMetrics;
  import org.wildfly.extension.picketlink.logging.PicketLinkLogger;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.net.URL;
  import java.util.ArrayList;
  import java.util.LinkedHashSet;
  import java.util.List;
  import java.util.Set;
  
  import static org.wildfly.extension.picketlink.logging.PicketLinkLogger.ROOT_LOGGER;
  
  /**
   * @author <a href="mailto:psilva@redhat.com">Pedro Silva</a>
   */
  public abstract class EntityProviderService<T extends PicketLinkFederationService<T>, C extends ProviderConfiguration> implements PicketLinkFederationService<T> {
  
      private final InjectedValue<FederationService> federationService = new InjectedValue<FederationService>();
      private final PicketLinkType picketLinkType;
      private volatile PicketLinkSubsystemMetrics metrics;
      private final Set<Handler> handlers = new LinkedHashSet<Handler>();
  
      public EntityProviderService(C configuration) {
          this.picketLinkType = createPicketLinkType(configuration);
      }
  
      @Override
      public void start(StartContext context) throws StartException {
          ROOT_LOGGER.debugf("Starting service for %s.", getConfiguration().getAlias());
      }
  
      @Override
      public void stop(StopContext context) {
          ROOT_LOGGER.debugf("Stopping service for %s.", getConfiguration().getAlias());
          context.getController().setMode(ServiceController.Mode.REMOVE);
      }
  
      @Override
      public void configure(DeploymentUnit deploymentUnit) {
          configureHandlers();
          configureWarMetadata(deploymentUnit);
          configureWebComponents(deploymentUnit);
          doConfigureDeployment(deploymentUnit);
          configureTokenProviders();
          configureKeyProvider();
      }
  
      @Override
      public PicketLinkSubsystemMetrics getMetrics() {
          if (this.metrics == null) {
              synchronized (this) {
                  if (this.metrics == null) {
                      try {
                          this.metrics = new PicketLinkSubsystemMetrics(((ProviderConfiguration) getPicketLinkType().getIdpOrSP()).getSecurityDomain());
                      } catch (ConfigurationException e) {
                          ROOT_LOGGER.federationErrorCollectingMetric(e);
                      }
                  }
              }
          }
  
          return this.metrics;
      }
  
      protected List<Class<? extends SAML2Handler>> getDefaultHandlers() {
          List<Class<? extends SAML2Handler>> defaultHandlers = new ArrayList<Class<? extends SAML2Handler>>();
  
          defaultHandlers.add(SAML2LogOutHandler.class);
          defaultHandlers.add(SAML2AuthenticationHandler.class);
          defaultHandlers.add(RolesGenerationHandler.class);
          defaultHandlers.add(SAML2EncryptionHandler.class);
          defaultHandlers.add(SAML2SignatureValidationHandler.class);
  
          return defaultHandlers;
      }
  
      private void configureKeyProvider() {
          getConfiguration().setKeyProvider(getFederationService().getValue().getKeyProviderType());
      }
  
      /**
       * <p> Configure the STS Token Providers. </p>
       */
      private void configureTokenProviders() {
          STSType stsType = getFederationService().getValue().getStsType();
  
          if (stsType != null) {
              int tokenTimeout = stsType.getTokenTimeout();
              int clockSkew = stsType.getClockSkew();
  
              STSType providerStsType = getPicketLinkType().getStsType();
  
              providerStsType.setTokenTimeout(tokenTimeout);
              providerStsType.setClockSkew(clockSkew);
  
              List<TokenProviderType> tokenProviders = providerStsType.getTokenProviders().getTokenProvider();
  
              for (TokenProviderType tokenProviderType : tokenProviders) {
                  if (tokenProviderType.getTokenType().equals(JBossSAMLURIConstants.ASSERTION_NSURI.get())) {
                      KeyValueType keyValueTypeTokenTimeout = new KeyValueType();
  
                      keyValueTypeTokenTimeout.setKey(GeneralConstants.ASSERTIONS_VALIDITY);
                      keyValueTypeTokenTimeout.setValue(String.valueOf(tokenTimeout));
  
                      KeyValueType keyValueTypeClockSkew = new KeyValueType();
  
                      keyValueTypeClockSkew.setKey(GeneralConstants.CLOCK_SKEW);
                      keyValueTypeClockSkew.setValue(String.valueOf(clockSkew));
  
                      tokenProviderType.add(keyValueTypeTokenTimeout);
                      tokenProviderType.add(keyValueTypeClockSkew);
                  }
              }
          }
      }
  
      /**
       * <p> Configure the SAML Handlers. </p>
       */
      private void configureHandlers() {
          Handlers actualHandlers = new Handlers();
  
          actualHandlers.setHandlers(new ArrayList<Handler>());
  
          if (this.handlers.isEmpty()) {
              for (Class<? extends SAML2Handler> commonHandlerClass : getDefaultHandlers()) {
                  addHandler(commonHandlerClass, actualHandlers);
              }
          } else {
              for (Handler handler : this.handlers) {
                  actualHandlers.add(handler);
              }
          }
  
          getPicketLinkType().setHandlers(actualHandlers);
      }
  
      private void configureWarMetadata(DeploymentUnit deploymentUnit) {
          WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
  
          warMetaData.getMergedJBossWebMetaData().setSecurityDomain(this.getConfiguration().getSecurityDomain());
      }
  
      protected abstract void configureWebComponents(DeploymentUnit deploymentUnit);
  
      /**
       * <p> Subclasses should implement this method to configureDeployment a specific PicketLink Provider type. Eg.: Identity Provider or Service
       * Provider. </p>
       *
       * @param deploymentUnit
       */
      protected abstract void doConfigureDeployment(DeploymentUnit deploymentUnit);
  
      @SuppressWarnings("unchecked")
      @Override
      public T getValue() throws IllegalStateException, IllegalArgumentException {
          return (T) this;
      }
  
      private PicketLinkType createPicketLinkType(C configuration) {
          PicketLinkType picketLinkType = new PicketLinkType();
  
          picketLinkType.setStsType(createSTSType());
          picketLinkType.setHandlers(new Handlers());
          picketLinkType.setEnableAudit(true);
  
          picketLinkType.setIdpOrSP((ProviderType) configuration);
  
          return picketLinkType;
      }
  
      public void addHandler(final Handler handler) {
          for (Handler actualHandler : new ArrayList<Handler>(this.handlers)) {
              if (actualHandler.getClazz().equals(handler.getClazz())) {
                  return;
              }
          }
  
          this.handlers.add(handler);
      }
  
      void addHandler(Class<? extends SAML2Handler> handlerClassName, Handlers handlers) {
          for (Handler handler : handlers.getHandler()) {
              if (handler.getClazz().equals(handlerClassName.getName())) {
                  return;
              }
          }
  
          Handler handler = new Handler();
  
          handler.setClazz(handlerClassName.getName());
  
          handlers.add(handler);
      }
  
      public void removeHandler(String handlerType) {
          for (Handler handler : new ArrayList<Handler>(this.handlers)) {
              if (handler.getClazz().equals(handlerType)) {
                  this.handlers.remove(handler);
              }
          }
      }
  
      @SuppressWarnings("unchecked")
      public C getConfiguration() {
          return (C) getPicketLinkType().getIdpOrSP();
      }
  
      private STSType createSTSType() {
          STSType stsType = null;
  
          InputStream stream = null;
  
          try {
              URL url = getClass().getClassLoader().getResource("core-sts.xml");
  
              if (url == null) {
                  url = Thread.currentThread().getContextClassLoader().getResource("core-sts");
              }
  
              if (url != null) {
                  stream = url.openStream();
                  stsType = (STSType) new STSConfigParser().parse(stream);
              }
          } catch (Exception e) {
              throw PicketLinkLogger.ROOT_LOGGER.federationCouldNotParseSTSConfig(e);
          } finally {
              try {
                  if (stream != null) {
                      stream.close();
                  }
              } catch (IOException ignored) {
              }
          }
  
          return stsType;
      }
  
      public InjectedValue<FederationService> getFederationService() {
          return this.federationService;
      }
  
      PicketLinkType getPicketLinkType() {
          return this.picketLinkType;
      }
  
      public void setConfiguration(C configuration) {
          this.picketLinkType.setIdpOrSP((ProviderType) configuration);
      }
  
      public void removeHandlerParameter(String handlerType, String handlerParameterName) {
          for (Handler handler : this.handlers) {
              if (handler.getClazz().equals(handlerType)) {
                  for (KeyValueType kv : new ArrayList<KeyValueType>(handler.getOption())) {
                      if (kv.getKey().equals(handlerParameterName)) {
                          handler.remove(kv);
                      }
                  }
              }
          }
      }
  
      public void addHandlerParameter(String handlerType, KeyValueType keyValueType) {
          removeHandlerParameter(handlerType, keyValueType.getKey());
  
          for (Handler handler : this.handlers) {
              if (handler.getClazz().equals(handlerType)) {
                  handler.add(keyValueType);
              }
          }
      }
  
      public static EntityProviderService getService(OperationContext context, String alias) {
          // We assume the mgmt ops that trigger IdentityProviderAddHandler or ServiceProviderAddHandler
          // run before the OperationStepHandler that triggers deploy. If not, that's a user mistake.
          // Since those handlers run first, we can count on MSC having services *registered* even
          // though we cannot count on them being *started*.
          ServiceRegistry serviceRegistry = context.getServiceRegistry(false);
          ServiceController<EntityProviderService> service = (ServiceController<EntityProviderService>) serviceRegistry.getService(IdentityProviderService.createServiceName(alias));
  
          if (service == null) {
              service = (ServiceController<EntityProviderService>) serviceRegistry.getService(ServiceProviderService.createServiceName(alias));
          }
  
          if (service == null) {
              return null;
          }
  
          return service.getValue();
      }
  }
  