package org.apereo.cas.support.wsfederation.authentication.crypto;

import org.apereo.cas.support.saml.InMemoryResourceMetadataResolver;
import org.apereo.cas.support.saml.OpenSamlConfigBean;
import org.apereo.cas.support.wsfederation.WsFederationConfiguration;
import org.apereo.cas.util.EncodingUtils;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
import org.jooq.lambda.Unchecked;
import org.opensaml.core.criterion.EntityIdCriterion;
import org.opensaml.saml.metadata.criteria.entity.impl.EvaluableEntityRoleEntityDescriptorCriterion;
import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.UsageType;
import org.opensaml.xmlsec.signature.X509Data;
import org.springframework.core.io.Resource;

import java.io.ByteArrayInputStream;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * This is {@link WsFederationMetadataCertificateProvider}.
 * OpenSAML does not yet support parsing out the WSFED metadata.
 * So instead we rely on the presence of {@link IDPSSODescriptor} and
 * its signing x509 certificate in the metadata to locate the certificate.
 * @author Misagh Moayyed
 * @since 6.5.0
 */
@RequiredArgsConstructor
@Slf4j
public class WsFederationMetadataCertificateProvider implements WsFederationCertificateProvider {
    private final Resource metadataResource;

    private final WsFederationConfiguration configuration;

    private final OpenSamlConfigBean openSamlConfigBean;

    @Override
    public List<Credential> getSigningCredentials() throws Exception {
        try (val is = metadataResource.getInputStream()) {
            val resolver = new InMemoryResourceMetadataResolver(is, openSamlConfigBean);
            resolver.setId(UUID.randomUUID().toString());
            resolver.initialize();
            val criteria = new CriteriaSet(new EntityIdCriterion(configuration.getIdentityProviderIdentifier()),
                new EvaluableEntityRoleEntityDescriptorCriterion(IDPSSODescriptor.DEFAULT_ELEMENT_NAME));
            LOGGER.debug("Locating entity descriptor in the metadata for [{}]", configuration.getIdentityProviderIdentifier());
            val entityDescriptor = resolver.resolveSingle(criteria);
            val roleDescriptors = entityDescriptor.getRoleDescriptors(IDPSSODescriptor.DEFAULT_ELEMENT_NAME);
            val keyDescriptors = roleDescriptors.get(0).getKeyDescriptors();
            val keyDescriptor = keyDescriptors
                .stream()
                .filter(key -> key.getUse() == UsageType.SIGNING)
                .findFirst()
                .orElseThrow(() -> new RuntimeException("Unable to find key descriptor marked for signing usage"));
            return keyDescriptor
                .getKeyInfo()
                .getX509Datas()
                .stream()
                .map(X509Data::getX509Certificates)
                .flatMap(List::stream)
                .map(Unchecked.function(cert -> {
                    LOGGER.debug("Parsing signing certificate [{}]", cert.getValue());
                    val decode = EncodingUtils.decodeBase64(cert.getValue());
                    try (val value = new ByteArrayInputStream(decode)) {
                        return WsFederationCertificateProvider.readCredential(value);
                    }
                }))
                .collect(Collectors.toList());
        }
    }
}

@@ -700,24 +700,79 @@
 Descriptor;%0A
+import org.opensaml.saml.saml2.metadata.KeyDescriptor;%0A
 import org.o
@@ -807,24 +807,24 @@
 Credential;%0A
-
 import org.o
@@ -862,16 +862,62 @@
 geType;%0A
+import org.opensaml.xmlsec.signature.KeyInfo;%0A
 import o
@@ -1001,16 +1001,16 @@
 ource;%0A%0A
-
 import j
@@ -1038,16 +1038,46 @@
 Stream;%0A
+import java.util.Collections;%0A
 import j
@@ -2625,35 +2625,39 @@
             val 
-key
+signing
 Descriptors = ro
@@ -2700,56 +2700,8 @@
 rs()
-;%0A            val keyDescriptor = keyDescriptors
 %0A   
@@ -2810,19 +2810,37 @@
    .
-findFir
+collect(Collectors.toLi
 st()
+);
 %0A   
@@ -2852,112 +2852,50 @@
     
-    .orElseThrow(() -%3E new RuntimeException(%22Unable to find key descriptor marked for signing usage%22)
+Collections.reverse(signingDescriptors
 );%0A
+%0A
     
@@ -2909,19 +2909,23 @@
  return 
-key
+signing
 Descript
@@ -2926,16 +2926,17 @@
 scriptor
+s
 %0A       
@@ -2945,16 +2945,61 @@
        .
+stream()%0A                .map(KeyDescriptor::
 getKeyIn
@@ -3000,17 +3000,16 @@
 tKeyInfo
-(
 )%0A      
@@ -3019,16 +3019,29 @@
        .
+map(KeyInfo::
 getX509D
@@ -3044,17 +3044,16 @@
 509Datas
-(
 )%0A      
@@ -3055,39 +3055,52 @@
                .
+flatMap(List::
 stream
-(
 )%0A              

check all signing keys when validating adfs response