Repository: Versent/saml2aws
Pull request #67 (2017-10-15 11:28:13)

refactor(Providers) Move providers into their own tree.


Patch:
diff --git a/.appveyor/appveyor.yml b/.appveyor/appveyor.yml
index 0cb36ac..c673744 100644
--- a/.appveyor/appveyor.yml
+++ b/.appveyor/appveyor.yml
@@ -1,4 +1,4 @@
-version: 1.8.4
+version: 1.7.{build}
 branches:
   only:
   - master
@@ -10,7 +10,7 @@ environment:
   CHOCOLATEY_APIKEY:
     secure: 3kWTz99Qj+ipyaR73CxcJeGRRbmk84MF2ERDu6MyY10cjHAi6s3AVZ2Ccoa+Ioyt
   appName: saml2aws
-  appVersion: 1.8.4
+  appVersion: 1.7.0
 install:
 - ps: >-
     write-output $env:GOPATH
diff --git a/.travis.yml b/.travis.yml
index 3c8a13a..a4e5b04 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,9 +1,9 @@
 language: go
 go:
-    - 1.8
+    - 1.9
 #    - tip
 
-go_import_path: github.com/versent/unicreds
+go_import_path: github.com/versent/saml2aws
 
 install:
     - echo noop
diff --git a/Makefile b/Makefile
index 4aeb822..24c7c8c 100644
--- a/Makefile
+++ b/Makefile
@@ -34,7 +34,7 @@ compile: deps
 	-osarch="windows/amd64" \
 	-osarch="windows/i386" \
 	-output "build/{{.Dir}}_$(VERSION)_{{.OS}}_{{.Arch}}/$(NAME)" \
-	$(shell glide novendor)
+	$(shell ./glide novendor)
 
 install:
 	go install ./cmd/saml2aws
@@ -52,7 +52,7 @@ release:
 	@github-release "v$(VERSION)" dist/* --commit "$(git rev-parse HEAD)" --github-repository versent/$(NAME)
 
 test: deps
-	go test -cover -v $(shell glide novendor)
+	go test -cover -v $(shell ./glide novendor)
 
 clean:
 	rm ./glide
diff --git a/choco/VERIFICATION.txt b/choco/VERIFICATION.txt
deleted file mode 100644
index 02109c0..0000000
--- a/choco/VERIFICATION.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-VERIFICATION
-Verification is intended to assist the Chocolatey moderators and community
-in verifying that this package's contents are trustworthy.
-
-The installer has been automatically built from source whch can be found on <http://github.com/Versent/saml2aws>
-and can be verified like this:
-
-1. Download the release version from <https://github.com/Versent/saml2aws/releases>
-2. You can use one of the following methods to obtain the checksum
-  - Use powershell function 'Get-Filehash'
-  - Use chocolatey utility 'checksum.exe'
-  Compare the checksums there with the checksum of the local binary in C:\ProgramData\Chocolatey\lib\saml2aws\src\saml2aws.exe
-
-File 'LICENSE.txt' is obtained from <http://github.com/Versent/saml2aws/LICENSE.md>.
\ No newline at end of file
diff --git a/choco/saml2aws.nuspec b/choco/saml2aws.nuspec
index 717a14a..55df560 100644
--- a/choco/saml2aws.nuspec
+++ b/choco/saml2aws.nuspec
@@ -4,7 +4,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
   <metadata>
     <id>saml2aws</id>
-    <version>1.8.4</version>
+    <version>1.7.0</version>
     <packageSourceUrl>https://github.com/Versent/saml2aws</packageSourceUrl>
     <!-- owners is a poor name for maintainers of the package. It sticks around by this name for compatibility reasons. It basically means you. -->
     <owners>Versent, https://github.com/Versent</owners>
@@ -16,7 +16,7 @@
     <projectSourceUrl>https://github.com/Versent/saml2aws</projectSourceUrl>
     <tags>saml2aws admin aws saml CLI</tags>
     <summary>CLI tool which enables you to login and retrieve AWS temporary credentials using a SAML IDP</summary>
-    <description>CLI tool which enables you to login and retrieve Amazon Web Services / AWS temporary credentials using a SAML IDP</description>
+    <description>CLI tool which enables you to login and retrieve AWS temporary credentials using a SAML IDP</description>
   </metadata>
   <files>
     <!-- this section controls what actually gets packaged into the Chocolatey package -->
diff --git a/cmd/saml2aws/commands/exec.go b/cmd/saml2aws/commands/exec.go
index b61a1b0..82df5fd 100644
--- a/cmd/saml2aws/commands/exec.go
+++ b/cmd/saml2aws/commands/exec.go
@@ -10,7 +10,7 @@ import (
 	"github.com/aws/aws-sdk-go/service/sts"
 	"github.com/pkg/errors"
 	"github.com/versent/saml2aws"
-	"github.com/versent/saml2aws/shell"
+	"github.com/versent/saml2aws/pkg/shell"
 )
 
 // Exec execute the supplied command after seeding the environment
diff --git a/cmd/saml2aws/commands/login.go b/cmd/saml2aws/commands/login.go
index 978f8b4..8899fe7 100644
--- a/cmd/saml2aws/commands/login.go
+++ b/cmd/saml2aws/commands/login.go
@@ -11,6 +11,7 @@ import (
 	"github.com/pkg/errors"
 	"github.com/versent/saml2aws"
 	"github.com/versent/saml2aws/helper/credentials"
+	"github.com/versent/saml2aws/pkg/creds"
 )
 
 // LoginFlags login specific command flags
@@ -47,7 +48,7 @@ func Login(loginFlags *LoginFlags) error {
 
 	// fmt.Println("LookupCredentials", hostname)
 
-	loginDetails := &saml2aws.LoginDetails{
+	loginDetails := &creds.LoginDetails{
 		Hostname: hostname,
 		Username: username,
 	}
@@ -160,7 +161,7 @@ func Login(loginFlags *LoginFlags) error {
 	return nil
 }
 
-func resolveLoginDetails(loginDetails *saml2aws.LoginDetails, loginFlags *LoginFlags) error {
+func resolveLoginDetails(loginDetails *creds.LoginDetails, loginFlags *LoginFlags) error {
 
 	// fmt.Printf("loginFlags %+v\n", loginFlags)
 
diff --git a/default.build.ps1 b/default.build.ps1
index 108d3e2..95dce5e 100644
--- a/default.build.ps1
+++ b/default.build.ps1
@@ -13,8 +13,6 @@ task 'Compile Go libraries...' {
 task 'Prepare for choco stuff...' {
   mkdir ./choco/src
   copy-item "./bin/${env:appName}.exe" "./choco/src/${env:appName}.exe"
-  Copy-Item "./LICENSE.md" "./choco/src/LICENSE.md"
-  Copy-Item "./choco/VERIFICATION.txt" "./choco/src/VERIFICATION.txt"
 }
 
 task 'Pack Choco...' {
diff --git a/helper/credentials/saml.go b/helper/credentials/saml.go
index 10bb53e..4d802ad 100644
--- a/helper/credentials/saml.go
+++ b/helper/credentials/saml.go
@@ -3,11 +3,11 @@ package credentials
 import (
 	"fmt"
 
-	"github.com/versent/saml2aws"
+	"github.com/versent/saml2aws/pkg/creds"
 )
 
 // LookupCredentials lookup an existing set of credentials and validate it.
-func LookupCredentials(loginDetails *saml2aws.LoginDetails) error {
+func LookupCredentials(loginDetails *creds.LoginDetails) error {
 
 	username, password, err := CurrentHelper.Get(fmt.Sprintf("https://%s", loginDetails.Hostname))
 	if err != nil {
diff --git a/input.go b/input.go
index 8d392e6..c29a4aa 100644
--- a/input.go
+++ b/input.go
@@ -7,33 +7,12 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/pkg/errors"
 	"github.com/segmentio/go-prompt"
+	"github.com/versent/saml2aws/pkg/creds"
 )
 
-// LoginDetails used to authenticate to ADFS
-type LoginDetails struct {
-	Username string
-	Password string
-	Hostname string
-}
-
-// Validate validate the login details
-func (ld *LoginDetails) Validate() error {
-	if ld.Hostname == "" {
-		return errors.New("Missing hostname")
-	}
-	if ld.Username == "" {
-		return errors.New("Missing username")
-	}
-	if ld.Password == "" {
-		return errors.New("Missing password")
-	}
-	return nil
-}
-
 // PromptForLoginDetails prompt the user to present their username, password and hostname
-func PromptForLoginDetails(loginDetails *LoginDetails) error {
+func PromptForLoginDetails(loginDetails *creds.LoginDetails) error {
 
 	loginDetails.Hostname = promptFor("Hostname [%s]", loginDetails.Hostname)
 
diff --git a/pkg/creds/creds.go b/pkg/creds/creds.go
new file mode 100644
index 0000000..08519c3
--- /dev/null
+++ b/pkg/creds/creds.go
@@ -0,0 +1,24 @@
+package creds
+
+import "errors"
+
+// LoginDetails used to authenticate
+type LoginDetails struct {
+	Username string
+	Password string
+	Hostname string
+}
+
+// Validate validate the login details
+func (ld *LoginDetails) Validate() error {
+	if ld.Hostname == "" {
+		return errors.New("Empty hostname")
+	}
+	if ld.Username == "" {
+		return errors.New("Empty username")
+	}
+	if ld.Password == "" {
+		return errors.New("Empty password")
+	}
+	return nil
+}
diff --git a/adfs.go b/pkg/provider/adfs/adfs.go
similarity index 90%
rename from adfs.go
rename to pkg/provider/adfs/adfs.go
index be49755..7dbbdac 100644
--- a/adfs.go
+++ b/pkg/provider/adfs/adfs.go
@@ -1,4 +1,4 @@
-package saml2aws
+package adfs
 
 import (
 	"bytes"
@@ -13,17 +13,18 @@ import (
 
 	"github.com/PuerkitoBio/goquery"
 	"github.com/pkg/errors"
+	"github.com/versent/saml2aws/pkg/creds"
 
 	"golang.org/x/net/publicsuffix"
 )
 
-// ADFSClient wrapper around ADFS enabling authentication and retrieval of assertions
-type ADFSClient struct {
+// Client wrapper around ADFS enabling authentication and retrieval of assertions
+type Client struct {
 	client *http.Client
 }
 
 // NewADFSClient create a new ADFS client
-func NewADFSClient(skipVerify bool) (*ADFSClient, error) {
+func NewADFSClient(skipVerify bool) (*Client, error) {
 
 	tr := &http.Transport{
 		TLSClientConfig: &tls.Config{InsecureSkipVerify: skipVerify, Renegotiation: tls.RenegotiateFreelyAsClient},
@@ -40,13 +41,13 @@ func NewADFSClient(skipVerify bool) (*ADFSClient, error) {
 
 	client := &http.Client{Transport: tr, Jar: jar}
 
-	return &ADFSClient{
+	return &Client{
 		client: client,
 	}, nil
 }
 
 // Authenticate authenticate to ADFS and return the data from the body of the SAML assertion.
-func (ac *ADFSClient) Authenticate(loginDetails *LoginDetails) (string, error) {
+func (ac *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) {
 	var authSubmitURL string
 	var samlAssertion string
 	authForm := url.Values{}
@@ -122,7 +123,7 @@ func (ac *ADFSClient) Authenticate(loginDetails *LoginDetails) (string, error) {
 	return samlAssertion, nil
 }
 
-func updateFormData(authForm url.Values, s *goquery.Selection, user *LoginDetails) {
+func updateFormData(authForm url.Values, s *goquery.Selection, user *creds.LoginDetails) {
 	name, ok := s.Attr("name")
 	//	log.Printf("name = %s ok = %v", name, ok)
 	if !ok {
diff --git a/adfs2.go b/pkg/provider/adfs2/adfs2.go
similarity index 83%
rename from adfs2.go
rename to pkg/provider/adfs2/adfs2.go
index 0b31662..764043f 100644
--- a/adfs2.go
+++ b/pkg/provider/adfs2/adfs2.go
@@ -1,4 +1,4 @@
-package saml2aws
+package adfs2
 
 import (
 	"bytes"
@@ -14,14 +14,17 @@ import (
 	"github.com/Azure/go-ntlmssp"
 	"github.com/PuerkitoBio/goquery"
 	"github.com/pkg/errors"
+	"github.com/versent/saml2aws/pkg/creds"
 )
 
-type ADFS2Client struct {
+// Client client for adfs2
+type Client struct {
 	transport http.RoundTripper
 	jar       http.CookieJar
 }
 
-func NewADFS2Client(skipVerify bool) (*ADFS2Client, error) {
+// NewADFS2Client new adfs2 client with ntlmssp configured
+func NewADFS2Client(skipVerify bool) (*Client, error) {
 	transport := &ntlmssp.Negotiator{
 		RoundTripper: &http.Transport{
 			TLSClientConfig: &tls.Config{InsecureSkipVerify: skipVerify, Renegotiation: tls.RenegotiateFreelyAsClient},
@@ -35,13 +38,14 @@ func NewADFS2Client(skipVerify bool) (*ADFS2Client, error) {
 		return nil, err
 	}
 
-	return &ADFS2Client{
+	return &Client{
 		transport: transport,
 		jar:       jar,
 	}, nil
 }
 
-func (ac *ADFS2Client) Authenticate(loginDetails *LoginDetails) (string, error) {
+// Authenticate authenticate the user using the supplied login details
+func (ac *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) {
 	var samlAssertion string
 	client := http.Client{
 		Transport: ac.transport,
diff --git a/jumpcloud.go b/pkg/provider/jumpcloud/jumpcloud.go
similarity index 92%
rename from jumpcloud.go
rename to pkg/provider/jumpcloud/jumpcloud.go
index dd7646c..f307ace 100644
--- a/jumpcloud.go
+++ b/pkg/provider/jumpcloud/jumpcloud.go
@@ -1,4 +1,4 @@
-package saml2aws
+package jumpcloud
 
 import (
 	"bytes"
@@ -14,17 +14,18 @@ import (
 	"github.com/PuerkitoBio/goquery"
 	"github.com/pkg/errors"
 	"github.com/segmentio/go-prompt"
+	"github.com/versent/saml2aws/pkg/creds"
 
 	"golang.org/x/net/publicsuffix"
 )
 
-// JumpCloudClient is a wrapper representing a JumpCloud SAML client
-type JumpCloudClient struct {
+// Client is a wrapper representing a JumpCloud SAML client
+type Client struct {
 	client *http.Client
 }
 
 // NewJumpCloudClient creates a new JumpCloud client
-func NewJumpCloudClient(skipVerify bool) (*JumpCloudClient, error) {
+func NewJumpCloudClient(skipVerify bool) (*Client, error) {
 	tr := &http.Transport{
 		TLSClientConfig: &tls.Config{InsecureSkipVerify: skipVerify},
 	}
@@ -40,13 +41,13 @@ func NewJumpCloudClient(skipVerify bool) (*JumpCloudClient, error) {
 
 	client := &http.Client{Transport: tr, Jar: jar}
 
-	return &JumpCloudClient{
+	return &Client{
 		client: client,
 	}, nil
 }
 
 // Authenticate logs into JumpCloud and returns a SAML response
-func (jc *JumpCloudClient) Authenticate(loginDetails *LoginDetails) (string, error) {
+func (jc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) {
 	var authSubmitURL string
 	var samlAssertion string
 	mfaRequired := false
@@ -159,7 +160,7 @@ func (jc *JumpCloudClient) Authenticate(loginDetails *LoginDetails) (string, err
 	return samlAssertion, nil
 }
 
-func updateJumpCloudForm(authForm url.Values, s *goquery.Selection, user *LoginDetails) {
+func updateJumpCloudForm(authForm url.Values, s *goquery.Selection, user *creds.LoginDetails) {
 	name, ok := s.Attr("name")
 	if !ok {
 		return
diff --git a/keycloak.go b/pkg/provider/keycloak/keycloak.go
similarity index 91%
rename from keycloak.go
rename to pkg/provider/keycloak/keycloak.go
index 3d72eee..c50fa26 100644
--- a/keycloak.go
+++ b/pkg/provider/keycloak/keycloak.go
@@ -1,4 +1,4 @@
-package saml2aws
+package keycloak
 
 import (
 	"bytes"
@@ -12,19 +12,20 @@ import (
 
 	"github.com/PuerkitoBio/goquery"
 	"github.com/pkg/errors"
+	"github.com/versent/saml2aws/pkg/creds"
 
 	"fmt"
 
 	"golang.org/x/net/publicsuffix"
 )
 
-// KeyCloakClient wrapper around KeyCloak.
-type KeyCloakClient struct {
+// Client wrapper around KeyCloak.
+type Client struct {
 	client *http.Client
 }
 
 // NewKeyCloakClient create a new KeyCloakClient
-func NewKeyCloakClient(skipVerify bool) (*KeyCloakClient, error) {
+func NewKeyCloakClient(skipVerify bool) (*Client, error) {
 
 	tr := &http.Transport{
 		TLSClientConfig: &tls.Config{InsecureSkipVerify: skipVerify},
@@ -42,13 +43,13 @@ func NewKeyCloakClient(skipVerify bool) (*KeyCloakClient, error) {
 
 	client := &http.Client{Transport: tr, Jar: jar}
 
-	return &KeyCloakClient{
+	return &Client{
 		client: client,
 	}, nil
 }
 
 // Authenticate logs into KeyCloak and returns a SAML response
-func (kc *KeyCloakClient) Authenticate(loginDetails *LoginDetails) (string, error) {
+func (kc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) {
 	var authSubmitURL string
 	var samlAssertion string
 	authForm := url.Values{}
@@ -130,7 +131,7 @@ func (kc *KeyCloakClient) Authenticate(loginDetails *LoginDetails) (string, erro
 	return samlAssertion, nil
 }
 
-func updateKeyCloakFormData(authForm url.Values, s *goquery.Selection, user *LoginDetails) {
+func updateKeyCloakFormData(authForm url.Values, s *goquery.Selection, user *creds.LoginDetails) {
 	name, ok := s.Attr("name")
 	// log.Printf("name = %s ok = %v", name, ok)
 	if !ok {
diff --git a/okta.go b/pkg/provider/okta/okta.go
similarity index 97%
rename from okta.go
rename to pkg/provider/okta/okta.go
index 0a378e2..d030b95 100644
--- a/okta.go
+++ b/pkg/provider/okta/okta.go
@@ -1,4 +1,4 @@
-package saml2aws
+package okta
 
 import (
 	"bytes"
@@ -16,14 +16,15 @@ import (
 	"github.com/pkg/errors"
 	prompt "github.com/segmentio/go-prompt"
 	"github.com/tidwall/gjson"
+	"github.com/versent/saml2aws/pkg/creds"
 
 	"encoding/json"
 
 	"golang.org/x/net/publicsuffix"
 )
 
-// OktaClient is a wrapper representing a Okta SAML client
-type OktaClient struct {
+// Client is a wrapper representing a Okta SAML client
+type Client struct {
 	client *http.Client
 }
 
@@ -39,7 +40,7 @@ type VerifyRequest struct {
 }
 
 // NewOktaClient creates a new Okta client
-func NewOktaClient(skipVerify bool) (*OktaClient, error) {
+func NewOktaClient(skipVerify bool) (*Client, error) {
 	tr := &http.Transport{
 		TLSClientConfig: &tls.Config{InsecureSkipVerify: skipVerify},
 	}
@@ -55,13 +56,13 @@ func NewOktaClient(skipVerify bool) (*OktaClient, error) {
 
 	client := &http.Client{Transport: tr, Jar: jar}
 
-	return &OktaClient{
+	return &Client{
 		client: client,
 	}, nil
 }
 
 // Authenticate logs into Okta and returns a SAML response
-func (oc *OktaClient) Authenticate(loginDetails *LoginDetails) (string, error) {
+func (oc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) {
 	var samlAssertion string
 
 	oktaEntryURL := fmt.Sprintf("https://%s", loginDetails.Hostname)
diff --git a/pingfed.go b/pkg/provider/pingfed/pingfed.go
similarity index 94%
rename from pingfed.go
rename to pkg/provider/pingfed/pingfed.go
index 9898175..5152df8 100644
--- a/pingfed.go
+++ b/pkg/provider/pingfed/pingfed.go
@@ -1,4 +1,4 @@
-package saml2aws
+package pingfed
 
 import (
 	"crypto/tls"
@@ -11,12 +11,13 @@ import (
 	"github.com/PuerkitoBio/goquery"
 	"github.com/pkg/errors"
 	prompt "github.com/segmentio/go-prompt"
+	"github.com/versent/saml2aws/pkg/creds"
 
 	"golang.org/x/net/publicsuffix"
 )
 
-// PingFedClient wrapper around PingFed + PingId enabling authentication and retrieval of assertions
-type PingFedClient struct {
+// Client wrapper around PingFed + PingId enabling authentication and retrieval of assertions
+type Client struct {
 	client        *http.Client
 	authSubmitURL string
 	samlAssertion string
@@ -24,7 +25,7 @@ type PingFedClient struct {
 }
 
 // NewPingFedClient create a new PingFed client
-func NewPingFedClient(skipVerify bool) (*PingFedClient, error) {
+func NewPingFedClient(skipVerify bool) (*Client, error) {
 
 	tr := &http.Transport{
 		TLSClientConfig: &tls.Config{InsecureSkipVerify: skipVerify},
@@ -45,14 +46,14 @@ func NewPingFedClient(skipVerify bool) (*PingFedClient, error) {
 		return errors.New("Redirect")
 	}
 
-	return &PingFedClient{
+	return &Client{
 		client:      client,
 		mfaRequired: false,
 	}, nil
 }
 
 // Authenticate Authenticate to PingFed and return the data from the body of the SAML assertion.
-func (ac *PingFedClient) Authenticate(loginDetails *LoginDetails) (string, error) {
+func (ac *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) {
 
 	authForm := url.Values{}
 
@@ -210,7 +211,7 @@ func (ac *PingFedClient) Authenticate(loginDetails *LoginDetails) (string, error
 	return ac.samlAssertion, nil
 }
 
-func updateLoginFormData(authForm url.Values, s *goquery.Selection, user *LoginDetails) {
+func updateLoginFormData(authForm url.Values, s *goquery.Selection, user *creds.LoginDetails) {
 	name, ok := s.Attr("name")
 	//	log.Printf("name = %s ok = %v", name, ok)
 	if !ok {
diff --git a/shell/env.go b/pkg/shell/env.go
similarity index 100%
rename from shell/env.go
rename to pkg/shell/env.go
diff --git a/shell/env_test.go b/pkg/shell/env_test.go
similarity index 100%
rename from shell/env_test.go
rename to pkg/shell/env_test.go
diff --git a/shell/shell.go b/pkg/shell/shell.go
similarity index 100%
rename from shell/shell.go
rename to pkg/shell/shell.go
diff --git a/shell/shell_test.go b/pkg/shell/shell_test.go
similarity index 100%
rename from shell/shell_test.go
rename to pkg/shell/shell_test.go
diff --git a/shell/shell_windows.go b/pkg/shell/shell_windows.go
similarity index 100%
rename from shell/shell_windows.go
rename to pkg/shell/shell_windows.go
diff --git a/saml2aws.go b/saml2aws.go
index 7aa0216..ee17930 100644
--- a/saml2aws.go
+++ b/saml2aws.go
@@ -1,6 +1,16 @@
 package saml2aws
 
-import "fmt"
+import (
+	"fmt"
+
+	"github.com/versent/saml2aws/pkg/creds"
+	"github.com/versent/saml2aws/pkg/provider/adfs"
+	"github.com/versent/saml2aws/pkg/provider/adfs2"
+	"github.com/versent/saml2aws/pkg/provider/jumpcloud"
+	"github.com/versent/saml2aws/pkg/provider/keycloak"
+	"github.com/versent/saml2aws/pkg/provider/okta"
+	"github.com/versent/saml2aws/pkg/provider/pingfed"
+)
 
 // Provider the SAML provider
 type Provider int
@@ -14,7 +24,7 @@ const (
 
 // SAMLClient client interface
 type SAMLClient interface {
-	Authenticate(loginDetails *LoginDetails) (string, error)
+	Authenticate(loginDetails *creds.LoginDetails) (string, error)
 }
 
 // SAMLOptions options for the new SAML client
@@ -27,17 +37,17 @@ type SAMLOptions struct {
 func NewSAMLClient(opts *SAMLOptions) (SAMLClient, error) {
 	switch opts.Provider {
 	case "ADFS":
-		return NewADFSClient(opts.SkipVerify)
+		return adfs.NewADFSClient(opts.SkipVerify)
 	case "ADFS2":
-		return NewADFS2Client(opts.SkipVerify)
+		return adfs2.NewADFS2Client(opts.SkipVerify)
 	case "Ping":
-		return NewPingFedClient(opts.SkipVerify)
+		return pingfed.NewPingFedClient(opts.SkipVerify)
 	case "JumpCloud":
-		return NewJumpCloudClient(opts.SkipVerify)
+		return jumpcloud.NewJumpCloudClient(opts.SkipVerify)
 	case "Okta":
-		return NewOktaClient(opts.SkipVerify)
+		return okta.NewOktaClient(opts.SkipVerify)
 	case "KeyCloak":
-		return NewKeyCloakClient(opts.SkipVerify)
+		return keycloak.NewKeyCloakClient(opts.SkipVerify)
 	default:
 		return nil, fmt.Errorf("Invalid provider: %v", opts.Provider)
 	}


Test patch:
diff --git a/cmd/saml2aws/commands/login_test.go b/cmd/saml2aws/commands/login_test.go
index 7edf4f3..72cb30e 100644
--- a/cmd/saml2aws/commands/login_test.go
+++ b/cmd/saml2aws/commands/login_test.go
@@ -5,18 +5,19 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	"github.com/versent/saml2aws"
+	"github.com/versent/saml2aws/pkg/creds"
 )
 
 func TestResolveLoginDetailsWithFlags(t *testing.T) {
 
 	loginFlags := &LoginFlags{Hostname: "id.example.com", Username: "wolfeidau", Password: "testtestlol", SkipPrompt: true}
 
-	loginDetails := &saml2aws.LoginDetails{Hostname: "id.example.com", Username: ""}
+	loginDetails := &creds.LoginDetails{Hostname: "id.example.com", Username: ""}
 
 	err := resolveLoginDetails(loginDetails, loginFlags)
 
 	assert.Empty(t, err)
-	assert.Equal(t, loginDetails, &saml2aws.LoginDetails{Username: "wolfeidau", Password: "testtestlol", Hostname: "id.example.com"})
+	assert.Equal(t, loginDetails, &creds.LoginDetails{Username: "wolfeidau", Password: "testtestlol", Hostname: "id.example.com"})
 }
 
 func TestResolveRoleSingleEntry(t *testing.T) {
diff --git a/input_test.go b/input_test.go
index ee8283d..50a0362 100644
--- a/input_test.go
+++ b/input_test.go
@@ -2,6 +2,8 @@ package saml2aws
 
 import (
 	"testing"
+
+	"github.com/versent/saml2aws/pkg/creds"
 )
 
 func TestLoginDetails_Validate(t *testing.T) {
@@ -23,7 +25,7 @@ func TestLoginDetails_Validate(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			ld := &LoginDetails{
+			ld := &creds.LoginDetails{
 				Username: tt.fields.Username,
 				Password: tt.fields.Password,
 				Hostname: tt.fields.Hostname,
diff --git a/pkg/creds/creds_test.go b/pkg/creds/creds_test.go
new file mode 100644
index 0000000..7b053e2
--- /dev/null
+++ b/pkg/creds/creds_test.go
@@ -0,0 +1,52 @@
+package creds
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestValidateEmptyLoginDetails(t *testing.T) {
+
+	ld := &LoginDetails{}
+
+	err := ld.Validate()
+
+	require.Error(t, err)
+}
+func TestValidateEmptyHostnameLoginDetails(t *testing.T) {
+
+	ld := &LoginDetails{Username: "test", Password: "test"}
+
+	err := ld.Validate()
+
+	require.Error(t, err)
+
+}
+
+func TestValidateEmptyUsernameLoginDetails(t *testing.T) {
+
+	ld := &LoginDetails{Hostname: "test", Password: "test"}
+
+	err := ld.Validate()
+
+	require.Error(t, err)
+
+}
+func TestValidateEmptyPasswordLoginDetails(t *testing.T) {
+
+	ld := &LoginDetails{Hostname: "test", Username: "test"}
+
+	err := ld.Validate()
+
+	require.Error(t, err)
+}
+
+func TestValidateLoginDetails(t *testing.T) {
+
+	ld := &LoginDetails{Hostname: "test", Username: "test", Password: "test"}
+
+	err := ld.Validate()
+
+	require.Nil(t, err)
+}