Repository: Versent/saml2aws
Pull request #360 (2019-10-10 01:51:51)

Remove PSU provider
This provider is, unfortunately, no longer usable for PSU, and is very unlikely usable for any other institution as Cosign is no longer maintained, and our implementation was highly customized. We have switched to the ShibbolethECP provider (See PR #314).


Patch:
diff --git a/README.md b/README.md
index ea16abd..70d46fa 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,6 @@ The process goes something like this:
   * [Google Apps](pkg/provider/googleapps/README.md)
   * [Shibboleth](pkg/provider/shibboleth/README.md)
   * [F5APM](pkg/provider/f5apm/README.md)
-  * [PSU](pkg/provider/psu/README.md)
 * AWS SAML Provider configured
 
 ## Caveats
diff --git a/go.mod b/go.mod
index 074024e..18b98ca 100644
--- a/go.mod
+++ b/go.mod
@@ -22,8 +22,6 @@ require (
 	github.com/godbus/dbus v4.1.0+incompatible // indirect
 	github.com/golang/protobuf v1.3.2 // indirect
 	github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
-	github.com/headzoo/surf v1.0.1-0.20180909134844-a4a8c16c01dc
-	github.com/headzoo/ut v0.0.0-20181013193318-a13b5a7a02ca // indirect
 	github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 // indirect
 	github.com/keybase/go-keychain v0.0.0-20181011010623-f1daa725cce4 // indirect
 	github.com/kr/pretty v0.1.0 // indirect
@@ -34,7 +32,6 @@ require (
 	github.com/onsi/ginkgo v1.10.1 // indirect
 	github.com/onsi/gomega v1.7.0 // indirect
 	github.com/pkg/errors v0.8.1
-	github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d
 	github.com/sirupsen/logrus v1.0.5
 	github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect
 	github.com/stretchr/objx v0.2.0 // indirect
@@ -50,5 +47,4 @@ require (
 	gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
 	gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
 	gopkg.in/ini.v1 v1.37.0
-	gopkg.in/sourcemap.v1 v1.0.5 // indirect
 )
diff --git a/go.sum b/go.sum
index bc709ab..a7edd3c 100644
--- a/go.sum
+++ b/go.sum
@@ -46,10 +46,6 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
 github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
-github.com/headzoo/surf v1.0.1-0.20180909134844-a4a8c16c01dc h1:xmXRlxaMHvNeB+EZ6HmWeLSifHbxQvZO/K1x9ICWOR0=
-github.com/headzoo/surf v1.0.1-0.20180909134844-a4a8c16c01dc/go.mod h1:/bct0m/iMNEqpn520y01yoaWxsAEigGFPnvyR1ewR5M=
-github.com/headzoo/ut v0.0.0-20181013193318-a13b5a7a02ca h1:utFgFwgxaqx5OthzE3DSGrtOq7rox5r2sxZ2wbfTuK0=
-github.com/headzoo/ut v0.0.0-20181013193318-a13b5a7a02ca/go.mod h1:8926sG02TCOX4RFRzIMFIzRw4xuc/TwO2gtN7teMJZ4=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE=
@@ -80,8 +76,6 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d h1:1VUlQbCfkoSGv7qP7Y+ro3ap1P1pPZxgdGVqiTVy5C4=
-github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
 github.com/sirupsen/logrus v1.0.5 h1:8c8b5uO0zS4X6RPl/sd1ENwSkIc0/H2PaHxE3udaE8I=
 github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
@@ -136,8 +130,6 @@ gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNj
 gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
 gopkg.in/ini.v1 v1.37.0 h1:Jg0A8mA6eCo6oGRA5ZgEwO9u/DIdlWMxyMzad9tN/rI=
 gopkg.in/ini.v1 v1.37.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
-gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
diff --git a/pkg/provider/psu/README.md b/pkg/provider/psu/README.md
deleted file mode 100644
index b0b7717..0000000
--- a/pkg/provider/psu/README.md
+++ /dev/null
@@ -1,21 +0,0 @@
-# PSU provider
-
-This provider authenticates via Penn State University's Cosign+Shibboleth
-implementation, then handles the typical WebAccess 2FA multi-factor
-authentication using either Duo or YubiKeys.
-
-## Instructions
-
-Uses default Shibboleth 3.3 pathing for the entry point.  e.g. if url is
-"https://idp.example.com" and the AWS URN is left as the default, this will
-construct the following URL to use.
-`https://idp.example.com/idp/profile/SAML2/Unsolicited/SSO?providerId=urn:amazon:webservices`
-
-To configure for PSU Access Shibboleth, run `saml2aws configure`, select PSU as
-the provider, and enter `https://as1.fim.psu.edu` for URL. Username is
-optional.
-
-## Features
-
-* Prompts for Duo MFA when logging in. Options are Duo Push, Phone Call, and
-  Passcode. Similar to the Duo SSH integration.
diff --git a/pkg/provider/psu/psu.go b/pkg/provider/psu/psu.go
deleted file mode 100644
index 0f0e7a9..0000000
--- a/pkg/provider/psu/psu.go
+++ /dev/null
@@ -1,261 +0,0 @@
-package psu
-
-import (
-	"encoding/json"
-	"fmt"
-	"github.com/headzoo/surf"
-	"github.com/headzoo/surf/browser"
-	"github.com/pkg/errors"
-	"github.com/robertkrimen/otto"
-	"github.com/sirupsen/logrus"
-	"github.com/versent/saml2aws/pkg/cfg"
-	"github.com/versent/saml2aws/pkg/creds"
-	"github.com/versent/saml2aws/pkg/prompter"
-	"github.com/versent/saml2aws/pkg/provider"
-	"regexp"
-	"strconv"
-	"time"
-)
-
-var logger = logrus.WithField("provider", "psu")
-
-// Client contains our browser and IDP Account configuration
-type Client struct {
-	b  *browser.Browser
-	ia *cfg.IDPAccount
-}
-
-// duoResults contains the extracted duoResults JS object from the 2FA page
-type duoResults struct {
-	AccountType string `json:"account_type"`
-	Devices     struct {
-		Devices []duoDevice `json:"devices"`
-	} `json:"devices"`
-	Error            string `json:"error"`
-	Referrer         string `json:"referrer"`
-	Remoteuser       string `json:"remoteuser"`
-	RequiredFactors  string `json:"requiredFactors"`
-	SatisfiedFactors string `json:"satisfiedFactors"`
-	Service          string `json:"service"`
-}
-
-// duoDevice describes a Duo 2FA device, its capabilities, etc
-// This is very similar to https://godoc.org/github.com/duosecurity/duo_api_golang/authapi#PreauthResult
-// but augmented for our purposes
-type duoDevice struct {
-	Capabilities []string `json:"capabilities"`
-	Device       string   `json:"device"`
-	DisplayName  string   `json:"display_name"`
-	SmsNextcode  string   `json:"sms_nextcode,omitempty"`
-	Type         string   `json:"type"`
-	OptionType   string   `json:"omitempty"`
-	Prompt       string   `json:"omitempty"`
-}
-
-// New returns a new psu.Client with the browser and idp account instantiated
-func New(idpAccount *cfg.IDPAccount) (*Client, error) {
-
-	tr := provider.NewDefaultTransport(idpAccount.SkipVerify)
-
-	// create our browser
-	b := surf.NewBrowser()
-	b.SetTimeout(time.Duration(idpAccount.Timeout) * time.Second)
-	b.SetTransport(tr)
-
-	return &Client{
-		b:  b,
-		ia: idpAccount,
-	}, nil
-}
-
-// Authenticate authenticates to PSU
-func (pc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) {
-
-	assertion, err := pc.login(loginDetails)
-
-	return assertion, err
-}
-
-func (pc *Client) login(loginDetails *creds.LoginDetails) (string, error) {
-	// Send our request to the IdP, which will redirect us to WebAccess
-	requestURL := fmt.Sprintf("%s/idp/profile/SAML2/Unsolicited/SSO?providerId=%s", loginDetails.URL, pc.ia.AmazonWebservicesURN)
-	logger.Debugf("Sending request to IdP: %s\n", requestURL)
-	err := pc.b.Open(requestURL)
-	if err != nil {
-		return "", errors.Wrapf(err, "Requesting initial IDP URL (%s)", requestURL)
-	}
-
-	logger.Debugf("Current URL: %s\n", pc.b.Url())
-
-	// find our login form
-	fm, err := pc.b.Form("form")
-	if err != nil {
-		return "", errors.Wrapf(err, "Unable to find login form on %s", pc.b.Url())
-	}
-
-	// submit username/password
-	logger.Debugf("Submitting creds to: %s\n", fm.Action())
-
-	err = fm.Input("login", loginDetails.Username)
-	if err != nil {
-		return "", errors.Wrap(err, "Could not find login input field")
-	}
-
-	err = fm.Input("password", loginDetails.Password)
-	if err != nil {
-		return "", errors.Wrap(err, "Could not find password input field")
-	}
-
-	err = fm.Submit()
-	if err != nil {
-		return "", errors.Wrapf(err, "Error when submitting creds to %s", fm.Action())
-	}
-
-	// find the 2fa form to make sure we are logged in before going any further
-	fm, err = pc.b.Form("form")
-	if err != nil {
-		return "", errors.Wrapf(err, "Could not locate 2FA form on %s, perhaps the login failed?", pc.b.Url())
-	}
-
-	// extract duoResults object from body text
-	dr, err := extractDuoResults(pc.b.Body())
-	if err != nil {
-		return "", errors.Wrapf(err, "Calling extractDuoResults() on 2FA login page body")
-	}
-
-	// parse duoResults into devices
-	duoDevices := parseDuoResults(dr)
-
-	// present list of duo options and prompt for input
-	fmt.Print("Enter a passcode or select one of the following options:\n\n")
-	for i, d := range duoDevices {
-		fmt.Printf(" %d. %s\n", i, d.Prompt)
-	}
-
-	fmt.Println() // get an extra space between options and input prompt
-
-	option := prompter.StringRequired("Passcode or option")
-	optint, err := strconv.Atoi(option) // try to convert input to int to partially validate it
-	if err != nil {
-		return "", errors.Wrapf(err, "Failed to convert %v to int", option)
-	}
-
-	// fill out 2FA form
-	if optint > len(duoDevices) {
-		// selection is larger than the number of options, assume it is a passcode
-		err = fm.Set("duo_passcode", option)
-		if err != nil {
-			return "", errors.Wrap(err, "Setting duo_passcode form field")
-		}
-
-		err = fm.Set("duo_factor", "passcode")
-		if err != nil {
-			return "", errors.Wrap(err, "Setting duo_factor form field")
-		}
-	} else {
-		// otherwise, set device and factor
-		err = fm.Input("duo_device", duoDevices[optint].Device)
-		if err != nil {
-			return "", errors.Wrap(err, "Setting duo_device form field")
-		}
-
-		err = fm.Set("duo_factor", duoDevices[optint].OptionType)
-		if err != nil {
-			return "", errors.Wrap(err, "Setting duo_factor form field")
-		}
-	}
-
-	// submit form
-	err = fm.Submit()
-	if err != nil {
-		return "", errors.Wrap(err, "Error when submitting form")
-	}
-
-	// pull the assertion out of the response
-	doc := pc.b.Dom()
-	s := doc.Find("input[name=SAMLResponse]").First()
-	assertion, ok := s.Attr("value")
-	if !ok {
-		return "", fmt.Errorf("Response from %s did not provide a SAML assertion (SAMLResponse html element)", pc.b.Url())
-	}
-	return assertion, nil
-}
-
-func stringInSlice(a string, list []string) bool {
-	for _, b := range list {
-		if b == a {
-			return true
-		}
-	}
-	return false
-}
-
-// build list of devices and options for the user prompt
-// it's simpler to do this in one block
-func parseDuoResults(dr duoResults) (devices []duoDevice) {
-	i := 0
-	for _, t := range []string{"push", "phone", "sms"} {
-		for _, d := range dr.Devices.Devices {
-			if stringInSlice(t, d.Capabilities) {
-				devices = append(devices, d)
-				devices[i].OptionType = t
-				switch t {
-				case "push":
-					devices[i].Prompt = fmt.Sprintf("Duo Push to %s", d.DisplayName)
-				case "phone":
-					devices[i].Prompt = fmt.Sprintf("Phone call to %s", d.DisplayName)
-				case "sms":
-					nextcode := ""
-					if d.SmsNextcode != "" {
-						nextcode = fmt.Sprintf(" (next code starts with %s)", d.SmsNextcode)
-					}
-					devices[i].Prompt = fmt.Sprintf("SMS passcodes to %s%s", d.DisplayName, nextcode)
-				}
-				i++
-			}
-		}
-	}
-	return
-}
-
-// extract duoResults from a body of text
-func extractDuoResults(body string) (dr duoResults, err error) {
-	// extract duoResults text from body
-	re := regexp.MustCompile(`var\s+duoResults\s+=\s+({[\S\s]*});`)
-	matches := re.FindStringSubmatch(body)
-	if len(matches) != 2 {
-		return dr, errors.New("Something went wrong, duoResults variable not present on page after submitting login")
-	}
-
-	// Create new JavaScript VM with a single variable called input, with our JS object assigned
-	vm := otto.New()
-	err = vm.Set("input", matches[1])
-	if err != nil {
-		return dr, errors.Wrap(err, "Setting JS VM input value")
-	}
-
-	// Call JavaScript's JSON.stringify() on the input variable we Set() above
-	stringifyOutput, err := vm.Run(`JSON.stringify( eval('('+input+')') )`)
-	if err != nil {
-		return dr, errors.Wrapf(err, "JSON.stringify returned `%s` when trying to extract Duo result JSON object", err)
-	}
-
-	// call otto's .ToString() on duoResultsJSON to turn it from a ott.Value to a string
-	duoResultsJSON, err := stringifyOutput.ToString()
-	if err != nil {
-		return dr, errors.Wrap(err, "ToString()")
-	}
-
-	// unmarshal JSON byte object into a duoResults struct
-	err = json.Unmarshal([]byte(duoResultsJSON), &dr)
-	if err != nil {
-		return dr, errors.Wrap(err, "Calling json.Unmarshal on duoResultsJSON")
-	}
-
-	// check that we didn't get a 0-length result
-	if len(dr.Devices.Devices) == 0 {
-		return dr, errors.New("No 2FA devices returned")
-	}
-
-	return
-}
diff --git a/saml2aws.go b/saml2aws.go
index 888ca8c..aa27950 100644
--- a/saml2aws.go
+++ b/saml2aws.go
@@ -17,7 +17,6 @@ import (
 	"github.com/versent/saml2aws/pkg/provider/onelogin"
 	"github.com/versent/saml2aws/pkg/provider/pingfed"
 	"github.com/versent/saml2aws/pkg/provider/pingone"
-	"github.com/versent/saml2aws/pkg/provider/psu"
 	"github.com/versent/saml2aws/pkg/provider/shibboleth"
 )
 
@@ -37,7 +36,6 @@ var MFAsByProvider = ProviderList{
 	"KeyCloak":   []string{"Auto"},                                       // automatically detects ToTP
 	"GoogleApps": []string{"Auto"},                                       // automatically detects ToTP
 	"Shibboleth": []string{"Auto"},
-	"PSU":        []string{"Auto"},
 	"F5APM":      []string{"Auto"},
 }
 
@@ -139,11 +137,6 @@ func NewSAMLClient(idpAccount *cfg.IDPAccount) (SAMLClient, error) {
 			return nil, fmt.Errorf("Invalid MFA type: %v for %v provider", idpAccount.MFA, idpAccount.Provider)
 		}
 		return shibboleth.New(idpAccount)
-	case "PSU":
-		if invalidMFA(idpAccount.Provider, idpAccount.MFA) {
-			return nil, fmt.Errorf("Invalid MFA type: %v for %v provider", idpAccount.MFA, idpAccount.Provider)
-		}
-		return psu.New(idpAccount)
 	case "F5APM":
 		if invalidMFA(idpAccount.Provider, idpAccount.MFA) {
 			return nil, fmt.Errorf("Invalid MFA type: %v for %v provider", idpAccount.MFA, idpAccount.Provider)


Test patch:
diff --git a/pkg/provider/psu/psu_test.go b/pkg/provider/psu/psu_test.go
deleted file mode 100644
index 9849ad4..0000000
--- a/pkg/provider/psu/psu_test.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package psu
-
-import (
-	"github.com/stretchr/testify/assert"
-	"testing"
-)
-
-var goodDuoResults = `<script type="text/javascript">var duoResults = {
-      devices: {"devices":[{"capabilities":["push","phone","sms"],"device":"abcdefg123456789","display_name":"Phone 1 (XXX-XXX-1234)","sms_nextcode":"1","type":"phone"},{"capabilities":["phone","sms"],"device":"987654321gfedcba","display_name":"Phone 2 (XXX-XXX-5678)","type":"phone"},{"capabilities":[],"device":"hijklmnop123456789","display_name":"12345678","type":"token"}]},
-      service: 'cosign-as1.fim.psu.edu',
-      referrer: 'https://as1.fim.psu.edu/idp/Authn/RemoteUser?conversation=e1s1',
-      remoteuser: '',
-      account_type: "dce",
-      requiredFactors: unescape('dce.psu.edu,2fa'),
-      satisfiedFactors: 'dce.psu.edu',
-      error: 'Additional authentication is required.',
-      onRedirect: function(url) {
-        window.location.assign(url);
-      }
-    };
-</script>`
-
-func TestStringInSlice(t *testing.T) {
-	needle := "needle"
-	haystackA := []string{"foo", "bar", "baz", "needle"}
-	haystackB := []string{"foo", "bar", "baz"}
-
-	assert.True(t, stringInSlice(needle, haystackA), "is true")
-	assert.False(t, stringInSlice(needle, haystackB), "is false")
-}
-
-func TestExtractDuoResults(t *testing.T) {
-
-	r, err := extractDuoResults(goodDuoResults)
-	assert.NoError(t, err)
-	assert.Len(t, r.Devices.Devices, 3)
-
-	_, err = extractDuoResults("")
-	assert.Error(t, err)
-
-	_, err = extractDuoResults(`var thisIsValidJson = {foo: "bar"};`)
-	assert.Error(t, err)
-
-	_, err = extractDuoResults(`var duoResults = {foo: "bar"};`)
-	assert.Error(t, err)
-
-	r, err = extractDuoResults(`var duoResults = { devices: {"devices":[]} };`)
-	assert.Error(t, err)
-	assert.Len(t, r.Devices.Devices, 0)
-}
-
-func TestParseDuoResults(t *testing.T) {
-
-	dr, err := extractDuoResults(goodDuoResults)
-	assert.Nil(t, err)
-
-	duoDevices := parseDuoResults(dr)
-	assert.Len(t, duoDevices, 5)
-
-	// push
-	assert.Equal(t, "push", duoDevices[0].OptionType)
-	assert.Equal(t, "abcdefg123456789", duoDevices[0].Device)
-	assert.Equal(t, "Duo Push to Phone 1 (XXX-XXX-1234)", duoDevices[0].Prompt)
-
-	// phone
-	assert.Equal(t, "phone", duoDevices[1].OptionType)
-	assert.Equal(t, "abcdefg123456789", duoDevices[1].Device)
-	assert.Equal(t, "Phone call to Phone 1 (XXX-XXX-1234)", duoDevices[1].Prompt)
-
-	// sms with next code
-	assert.Equal(t, "sms", duoDevices[3].OptionType, "sms")
-	assert.Equal(t, "abcdefg123456789", duoDevices[3].Device)
-	assert.Equal(t, "SMS passcodes to Phone 1 (XXX-XXX-1234) (next code starts with 1)", duoDevices[3].Prompt)
-
-	// sms without next code
-	assert.Equal(t, "sms", duoDevices[4].OptionType, "sms")
-	assert.Equal(t, "987654321gfedcba", duoDevices[4].Device)
-	assert.Equal(t, "SMS passcodes to Phone 2 (XXX-XXX-5678)", duoDevices[4].Prompt)
-}
diff --git a/saml2aws_test.go b/saml2aws_test.go
index 33700e7..2fc369d 100644
--- a/saml2aws_test.go
+++ b/saml2aws_test.go
@@ -10,7 +10,7 @@ func TestProviderList_Keys(t *testing.T) {
 
 	names := MFAsByProvider.Names()
 
-	require.Len(t, names, 13)
+	require.Len(t, names, 12)
 
 }