<commit_before>package server

import (
	"encoding/json"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"path"
	"time"

	"github.com/crewjam/saml"
	logrus "github.com/sirupsen/logrus"

	"github.com/pachyderm/pachyderm/src/server/pkg/backoff"
)

func validateConfig() error {
	// TODO - take the validation stuff below (that's called by watchConfig) and move it in here (called by setConfig
	return nil
}

// lookupIDPMetadata takes the URL of a SAML IdP's Metadata service, queries it,
// parses the result, and returns it as a struct the crewjam/saml library can
// use
// This code is heavily based on the crewjam/saml/samlsp.Middleware constructor
func lookupIDPMetadata(name string, mdURL *url.URL) (*saml.EntityDescriptor, error) {
	c := http.DefaultClient
	req, err := http.NewRequest("GET", mdURL.String(), nil)
	if err != nil {
		return nil, fmt.Errorf("could not retrieve IdP metadata for %q: %v", name, err)
	}
	req.Header.Set("User-Agent", "Golang; github.com/pachyderm/pachdyerm")

	var rawMetadata []byte
	b := backoff.NewInfiniteBackOff()
	b.MaxElapsedTime = 90 * time.Second
	b.MaxInterval = 2 * time.Second
	backoff.RetryNotify(func() error {
		resp, err := c.Do(req)
		if err != nil {
			return err
		}

		if resp.StatusCode != http.StatusOK {
			return fmt.Errorf("%d %s", resp.StatusCode, resp.Status)
		}
		rawMetadata, err = ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		if err != nil {
			return fmt.Errorf("could not read IdP metadata response body: %v", err)
		}
		if len(rawMetadata) == 0 {
			return fmt.Errorf("empty metadata from IdP")
		}
		return nil
	}, b, func(err error, d time.Duration) error {
		logrus.Printf("error retrieving IdP metadata: %v; retrying in %v", err, d)
		return nil
	})

	// Successfully retrieved metadata--try parsing it
	entity := &saml.EntityDescriptor{}
	err = xml.Unmarshal(rawMetadata, entity)
	if err != nil {
		// this comparison is ugly, but it is how the error is generated in
		// encoding/xml
		if err.Error() != "expected element type <EntityDescriptor> but have <EntitiesDescriptor>" {
			return nil, fmt.Errorf("could not unmarshal EntityDescriptor from IdP metadata: %v", err)
		}
		// Search through <EntitiesDescriptor> & find IdP entity
		entities := &saml.EntitiesDescriptor{}
		if err := xml.Unmarshal(rawMetadata, entities); err != nil {
			return nil, fmt.Errorf("could not unmarshal EntitiesDescriptor from IdP metadata: %v", err)
		}
		for i, e := range entities.EntityDescriptors {
			if len(e.IDPSSODescriptors) > 0 {
				entity = &entities.EntityDescriptors[i]
				break
			}
		}
		// Make sure we found an IdP entity descriptor
		if len(entity.IDPSSODescriptors) == 0 {
			return nil, fmt.Errorf("no entity found with IDPSSODescriptor")
		}
	}
	return entity, nil
}

// updateSAMLSP Updates the saml SP library object in 'apiServer' after a new
// config update has been observed
// CAUTION: the caller should already hold a lock on a.configCache
func (a *apiServer) updateSAMLSP() error {
	a.samlSPMu.Lock()
	defer a.samlSPMu.Unlock()

	if a.configCache.SAMLServiceOptions == nil {<commit_msg>Shorten deadline fetching IdP metadata 90s->30s<commit_after>package server

import (
	"encoding/json"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"path"
	"time"

	"github.com/crewjam/saml"
	logrus "github.com/sirupsen/logrus"

	"github.com/pachyderm/pachyderm/src/server/pkg/backoff"
)

func validateConfig() error {
	// TODO - take the validation stuff below (that's called by watchConfig) and move it in here (called by setConfig
	return nil
}

// lookupIDPMetadata takes the URL of a SAML IdP's Metadata service, queries it,
// parses the result, and returns it as a struct the crewjam/saml library can
// use
// This code is heavily based on the crewjam/saml/samlsp.Middleware constructor
func lookupIDPMetadata(name string, mdURL *url.URL) (*saml.EntityDescriptor, error) {
	c := http.DefaultClient
	req, err := http.NewRequest("GET", mdURL.String(), nil)
	if err != nil {
		return nil, fmt.Errorf("could not retrieve IdP metadata for %q: %v", name, err)
	}
	req.Header.Set("User-Agent", "Golang; github.com/pachyderm/pachdyerm")

	var rawMetadata []byte
	b := backoff.NewInfiniteBackOff()
	b.MaxElapsedTime = 30 * time.Second
	b.MaxInterval = 2 * time.Second
	backoff.RetryNotify(func() error {
		resp, err := c.Do(req)
		if err != nil {
			return err
		}

		if resp.StatusCode != http.StatusOK {
			return fmt.Errorf("%d %s", resp.StatusCode, resp.Status)
		}
		rawMetadata, err = ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		if err != nil {
			return fmt.Errorf("could not read IdP metadata response body: %v", err)
		}
		if len(rawMetadata) == 0 {
			return fmt.Errorf("empty metadata from IdP")
		}
		return nil
	}, b, func(err error, d time.Duration) error {
		logrus.Printf("error retrieving IdP metadata: %v; retrying in %v", err, d)
		return nil
	})

	// Successfully retrieved metadata--try parsing it
	entity := &saml.EntityDescriptor{}
	err = xml.Unmarshal(rawMetadata, entity)
	if err != nil {
		// this comparison is ugly, but it is how the error is generated in
		// encoding/xml
		if err.Error() != "expected element type <EntityDescriptor> but have <EntitiesDescriptor>" {
			return nil, fmt.Errorf("could not unmarshal EntityDescriptor from IdP metadata: %v", err)
		}
		// Search through <EntitiesDescriptor> & find IdP entity
		entities := &saml.EntitiesDescriptor{}
		if err := xml.Unmarshal(rawMetadata, entities); err != nil {
			return nil, fmt.Errorf("could not unmarshal EntitiesDescriptor from IdP metadata: %v", err)
		}
		for i, e := range entities.EntityDescriptors {
			if len(e.IDPSSODescriptors) > 0 {
				entity = &entities.EntityDescriptors[i]
				break
			}
		}
		// Make sure we found an IdP entity descriptor
		if len(entity.IDPSSODescriptors) == 0 {
			return nil, fmt.Errorf("no entity found with IDPSSODescriptor")
		}
	}
	return entity, nil
}

// updateSAMLSP Updates the saml SP library object in 'apiServer' after a new
// config update has been observed
// CAUTION: the caller should already hold a.configMu
func (a *apiServer) updateSAMLSP() error {
	a.samlSPMu.Lock()
	defer a.samlSPMu.Unlock()

	if a.configCache.SAMLServiceOptions == nil {
{'fraction_non_alphanumeric': 0.12065930548887822, 'fraction_numerical': 0.0025604096655464873, 'mean_word_length': 3.875195007800312, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 12, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 8}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '496722', 'n_tokens_mistral': 2247, 'n_tokens_neox': 2101, 'n_words': 650}