github.com/versent/saml2aws@v2.17.0+incompatible/pkg/provider/aad/aad.go (about)

     1  package aad
     2  
     3  import (
     4  	"bufio"
     5  	"crypto/tls"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"net/url"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/PuerkitoBio/goquery"
    16  	"github.com/pkg/errors"
    17  	"github.com/sirupsen/logrus"
    18  	"github.com/versent/saml2aws/pkg/cfg"
    19  	"github.com/versent/saml2aws/pkg/creds"
    20  	"github.com/versent/saml2aws/pkg/prompter"
    21  	"github.com/versent/saml2aws/pkg/provider"
    22  )
    23  
    24  var logger = logrus.WithField("provider", "aad")
    25  
    26  // Client wrapper around AzureAD enabling authentication and retrieval of assertions
    27  type Client struct {
    28  	client     *provider.HTTPClient
    29  	idpAccount *cfg.IDPAccount
    30  }
    31  
    32  // Autogenerate startSAML Response struct
    33  // some case, some fiels is not exists
    34  type startSAMLResponse struct {
    35  	FShowPersistentCookiesWarning         bool     `json:"fShowPersistentCookiesWarning"`
    36  	URLMsaLogout                          string   `json:"urlMsaLogout"`
    37  	ShowCantAccessAccountLink             bool     `json:"showCantAccessAccountLink"`
    38  	URLGitHubFed                          string   `json:"urlGitHubFed"`
    39  	FShowSignInWithGitHubOnlyOnCredPicker bool     `json:"fShowSignInWithGitHubOnlyOnCredPicker"`
    40  	FEnableShowResendCode                 bool     `json:"fEnableShowResendCode"`
    41  	IShowResendCodeDelay                  int      `json:"iShowResendCodeDelay"`
    42  	SSMSCtryPhoneData                     string   `json:"sSMSCtryPhoneData"`
    43  	FUseInlinePhoneNumber                 bool     `json:"fUseInlinePhoneNumber"`
    44  	URLSessionState                       string   `json:"urlSessionState"`
    45  	URLResetPassword                      string   `json:"urlResetPassword"`
    46  	URLMsaResetPassword                   string   `json:"urlMsaResetPassword"`
    47  	URLLogin                              string   `json:"urlLogin"`
    48  	URLSignUp                             string   `json:"urlSignUp"`
    49  	URLGetCredentialType                  string   `json:"urlGetCredentialType"`
    50  	URLGetOneTimeCode                     string   `json:"urlGetOneTimeCode"`
    51  	URLLogout                             string   `json:"urlLogout"`
    52  	URLForget                             string   `json:"urlForget"`
    53  	URLDisambigRename                     string   `json:"urlDisambigRename"`
    54  	URLGoToAADError                       string   `json:"urlGoToAADError"`
    55  	URLDssoStatus                         string   `json:"urlDssoStatus"`
    56  	URLFidoHelp                           string   `json:"urlFidoHelp"`
    57  	URLFidoLogin                          string   `json:"urlFidoLogin"`
    58  	URLPostAad                            string   `json:"urlPostAad"`
    59  	URLPostMsa                            string   `json:"urlPostMsa"`
    60  	URLPIAEndAuth                         string   `json:"urlPIAEndAuth"`
    61  	FCBShowSignUp                         bool     `json:"fCBShowSignUp"`
    62  	FKMSIEnabled                          bool     `json:"fKMSIEnabled"`
    63  	ILoginMode                            int      `json:"iLoginMode"`
    64  	FAllowPhoneSignIn                     bool     `json:"fAllowPhoneSignIn"`
    65  	FAllowPhoneInput                      bool     `json:"fAllowPhoneInput"`
    66  	FAllowSkypeNameLogin                  bool     `json:"fAllowSkypeNameLogin"`
    67  	IMaxPollErrors                        int      `json:"iMaxPollErrors"`
    68  	IPollingTimeout                       int      `json:"iPollingTimeout"`
    69  	SrsSuccess                            bool     `json:"srsSuccess"`
    70  	FShowSwitchUser                       bool     `json:"fShowSwitchUser"`
    71  	ArrValErrs                            []string `json:"arrValErrs"`
    72  	SErrorCode                            string   `json:"sErrorCode"`
    73  	SErrTxt                               string   `json:"sErrTxt"`
    74  	SResetPasswordPrefillParam            string   `json:"sResetPasswordPrefillParam"`
    75  	OnPremPasswordValidationConfig        struct {
    76  		IsUserRealmPrecheckEnabled bool `json:"isUserRealmPrecheckEnabled"`
    77  	} `json:"onPremPasswordValidationConfig"`
    78  	FSwitchDisambig   bool `json:"fSwitchDisambig"`
    79  	OCancelPostParams struct {
    80  		Error        string `json:"error"`
    81  		ErrorSubcode string `json:"error_subcode"`
    82  		State        string `json:"state"`
    83  	} `json:"oCancelPostParams"`
    84  	IAllowedIdentities                  int         `json:"iAllowedIdentities"`
    85  	IRemoteNgcPollingType               int         `json:"iRemoteNgcPollingType"`
    86  	IsGlobalTenant                      bool        `json:"isGlobalTenant"`
    87  	FIsFidoSupported                    bool        `json:"fIsFidoSupported"`
    88  	FUseNewNoPasswordTypes              bool        `json:"fUseNewNoPasswordTypes"`
    89  	IMaxStackForKnockoutAsyncComponents int         `json:"iMaxStackForKnockoutAsyncComponents"`
    90  	StrCopyrightTxt                     string      `json:"strCopyrightTxt"`
    91  	FShowButtons                        bool        `json:"fShowButtons"`
    92  	URLCdn                              string      `json:"urlCdn"`
    93  	URLFooterTOU                        string      `json:"urlFooterTOU"`
    94  	URLFooterPrivacy                    string      `json:"urlFooterPrivacy"`
    95  	URLPost                             string      `json:"urlPost"`
    96  	URLRefresh                          string      `json:"urlRefresh"`
    97  	URLCancel                           string      `json:"urlCancel"`
    98  	IPawnIcon                           int         `json:"iPawnIcon"`
    99  	IPollingInterval                    int         `json:"iPollingInterval"`
   100  	SPOSTUsername                       string      `json:"sPOST_Username"`
   101  	SFT                                 string      `json:"sFT"`
   102  	SFTName                             string      `json:"sFTName"`
   103  	SSessionIdentifierName              string      `json:"sSessionIdentifierName"`
   104  	SCtx                                string      `json:"sCtx"`
   105  	IProductIcon                        int         `json:"iProductIcon"`
   106  	URLReportPageLoad                   string      `json:"urlReportPageLoad"`
   107  	StaticTenantBranding                interface{} `json:"staticTenantBranding"`
   108  	OAppCobranding                      struct {
   109  	} `json:"oAppCobranding"`
   110  	IBackgroundImage                      int           `json:"iBackgroundImage"`
   111  	ArrSessions                           []interface{} `json:"arrSessions"`
   112  	FUseConstantPolling                   bool          `json:"fUseConstantPolling"`
   113  	FUseFlowTokenAsCanary                 bool          `json:"fUseFlowTokenAsCanary"`
   114  	FApplicationInsightsEnabled           bool          `json:"fApplicationInsightsEnabled"`
   115  	IApplicationInsightsEnabledPercentage int           `json:"iApplicationInsightsEnabledPercentage"`
   116  	URLSetDebugMode                       string        `json:"urlSetDebugMode"`
   117  	FEnableCSSAnimation                   bool          `json:"fEnableCssAnimation"`
   118  	FAllowGrayOutLightBox                 bool          `json:"fAllowGrayOutLightBox"`
   119  	FIsRemoteNGCSupported                 bool          `json:"fIsRemoteNGCSupported"`
   120  	Scid                                  int           `json:"scid"`
   121  	Hpgact                                int           `json:"hpgact"`
   122  	Hpgid                                 int           `json:"hpgid"`
   123  	Pgid                                  string        `json:"pgid"`
   124  	APICanary                             string        `json:"apiCanary"`
   125  	Canary                                string        `json:"canary"`
   126  	CorrelationID                         string        `json:"correlationId"`
   127  	SessionID                             string        `json:"sessionId"`
   128  	Locale                                struct {
   129  		Mkt  string `json:"mkt"`
   130  		Lcid int    `json:"lcid"`
   131  	} `json:"locale"`
   132  	SlMaxRetry      int  `json:"slMaxRetry"`
   133  	SlReportFailure bool `json:"slReportFailure"`
   134  	Strings         struct {
   135  		Desktopsso struct {
   136  			Authenticatingmessage string `json:"authenticatingmessage"`
   137  		} `json:"desktopsso"`
   138  	} `json:"strings"`
   139  	Enums struct {
   140  		ClientMetricsModes struct {
   141  			None             int `json:"None"`
   142  			SubmitOnPost     int `json:"SubmitOnPost"`
   143  			SubmitOnRedirect int `json:"SubmitOnRedirect"`
   144  			InstrumentPlt    int `json:"InstrumentPlt"`
   145  		} `json:"ClientMetricsModes"`
   146  	} `json:"enums"`
   147  	Urls struct {
   148  		Instr struct {
   149  			Pageload   string `json:"pageload"`
   150  			Dssostatus string `json:"dssostatus"`
   151  		} `json:"instr"`
   152  	} `json:"urls"`
   153  	Browser struct {
   154  		Ltr     int `json:"ltr"`
   155  		Other   int `json:"_Other"`
   156  		Full    int `json:"Full"`
   157  		REOther int `json:"RE_Other"`
   158  		B       struct {
   159  			Name  string `json:"name"`
   160  			Major int    `json:"major"`
   161  			Minor int    `json:"minor"`
   162  		} `json:"b"`
   163  		Os struct {
   164  			Name    string `json:"name"`
   165  			Version string `json:"version"`
   166  		} `json:"os"`
   167  		V int `json:"V"`
   168  	} `json:"browser"`
   169  	Watson struct {
   170  		URL              string   `json:"url"`
   171  		Bundle           string   `json:"bundle"`
   172  		Sbundle          string   `json:"sbundle"`
   173  		Fbundle          string   `json:"fbundle"`
   174  		ResetErrorPeriod int      `json:"resetErrorPeriod"`
   175  		MaxCorsErrors    int      `json:"maxCorsErrors"`
   176  		MaxInjectErrors  int      `json:"maxInjectErrors"`
   177  		MaxErrors        int      `json:"maxErrors"`
   178  		MaxTotalErrors   int      `json:"maxTotalErrors"`
   179  		ExpSrcs          []string `json:"expSrcs"`
   180  		EnvErrorRedirect bool     `json:"envErrorRedirect"`
   181  		EnvErrorURL      string   `json:"envErrorUrl"`
   182  	} `json:"watson"`
   183  	Loader struct {
   184  		CdnRoots []string `json:"cdnRoots"`
   185  	} `json:"loader"`
   186  	ServerDetails struct {
   187  		Slc string `json:"slc"`
   188  		Dc  string `json:"dc"`
   189  		Ri  string `json:"ri"`
   190  		Ver struct {
   191  			V []int `json:"v"`
   192  		} `json:"ver"`
   193  		Rt string `json:"rt"`
   194  		Et int    `json:"et"`
   195  	} `json:"serverDetails"`
   196  	Country                    string `json:"country"`
   197  	FBreakBrandingSigninString bool   `json:"fBreakBrandingSigninString"`
   198  	Bsso                       struct {
   199  		Type   string `json:"type"`
   200  		Reason string `json:"reason"`
   201  	} `json:"bsso"`
   202  	URLNoCookies       string `json:"urlNoCookies"`
   203  	FTrimChromeBssoURL bool   `json:"fTrimChromeBssoUrl"`
   204  }
   205  
   206  // Autogenerate password login response
   207  // some case, some fiels is not exists
   208  type passwordLoginResponse struct {
   209  	ArrUserProofs []struct {
   210  		AuthMethodID string `json:"authMethodId"`
   211  		Data         string `json:"data"`
   212  		Display      string `json:"display"`
   213  		IsDefault    bool   `json:"isDefault"`
   214  	} `json:"arrUserProofs"`
   215  	FHideIHaveCodeLink                  bool               `json:"fHideIHaveCodeLink"`
   216  	OPerAuthPollingInterval             map[string]float64 `json:"oPerAuthPollingInterval"`
   217  	FProofIndexedByType                 bool               `json:"fProofIndexedByType"`
   218  	URLBeginAuth                        string             `json:"urlBeginAuth"`
   219  	URLEndAuth                          string             `json:"urlEndAuth"`
   220  	ISAMode                             int                `json:"iSAMode"`
   221  	ITrustedDeviceCheckboxConfig        int                `json:"iTrustedDeviceCheckboxConfig"`
   222  	IMaxPollAttempts                    int                `json:"iMaxPollAttempts"`
   223  	IPollingTimeout                     int                `json:"iPollingTimeout"`
   224  	IPollingBackoffInterval             float64            `json:"iPollingBackoffInterval"`
   225  	IRememberMfaDuration                float64            `json:"iRememberMfaDuration"`
   226  	STrustedDeviceCheckboxName          string             `json:"sTrustedDeviceCheckboxName"`
   227  	SAuthMethodInputFieldName           string             `json:"sAuthMethodInputFieldName"`
   228  	ISAOtcLength                        int                `json:"iSAOtcLength"`
   229  	ITotpOtcLength                      int                `json:"iTotpOtcLength"`
   230  	URLMoreInfo                         string             `json:"urlMoreInfo"`
   231  	FShowViewDetailsLink                bool               `json:"fShowViewDetailsLink"`
   232  	FAlwaysUpdateFTInSasEnd             bool               `json:"fAlwaysUpdateFTInSasEnd"`
   233  	IMaxStackForKnockoutAsyncComponents int                `json:"iMaxStackForKnockoutAsyncComponents"`
   234  	StrCopyrightTxt                     string             `json:"strCopyrightTxt"`
   235  	FShowButtons                        bool               `json:"fShowButtons"`
   236  	URLCdn                              string             `json:"urlCdn"`
   237  	URLFooterTOU                        string             `json:"urlFooterTOU"`
   238  	URLFooterPrivacy                    string             `json:"urlFooterPrivacy"`
   239  	URLPost                             string             `json:"urlPost"`
   240  	URLCancel                           string             `json:"urlCancel"`
   241  	IPawnIcon                           int                `json:"iPawnIcon"`
   242  	IPollingInterval                    int                `json:"iPollingInterval"`
   243  	SPOSTUsername                       string             `json:"sPOST_Username"`
   244  	SFT                                 string             `json:"sFT"`
   245  	SFTName                             string             `json:"sFTName"`
   246  	SCtx                                string             `json:"sCtx"`
   247  	DynamicTenantBranding               []struct {
   248  		Locale                 int    `json:"Locale"`
   249  		Illustration           string `json:"Illustration"`
   250  		UserIDLabel            string `json:"UserIdLabel"`
   251  		KeepMeSignedInDisabled bool   `json:"KeepMeSignedInDisabled"`
   252  		UseTransparentLightBox bool   `json:"UseTransparentLightBox"`
   253  	} `json:"dynamicTenantBranding"`
   254  	OAppCobranding struct {
   255  	} `json:"oAppCobranding"`
   256  	IBackgroundImage                      int    `json:"iBackgroundImage"`
   257  	FUseConstantPolling                   bool   `json:"fUseConstantPolling"`
   258  	FUseFlowTokenAsCanary                 bool   `json:"fUseFlowTokenAsCanary"`
   259  	FApplicationInsightsEnabled           bool   `json:"fApplicationInsightsEnabled"`
   260  	IApplicationInsightsEnabledPercentage int    `json:"iApplicationInsightsEnabledPercentage"`
   261  	URLSetDebugMode                       string `json:"urlSetDebugMode"`
   262  	FEnableCSSAnimation                   bool   `json:"fEnableCssAnimation"`
   263  	FAllowGrayOutLightBox                 bool   `json:"fAllowGrayOutLightBox"`
   264  	FIsRemoteNGCSupported                 bool   `json:"fIsRemoteNGCSupported"`
   265  	Scid                                  int    `json:"scid"`
   266  	Hpgact                                int    `json:"hpgact"`
   267  	Hpgid                                 int    `json:"hpgid"`
   268  	Pgid                                  string `json:"pgid"`
   269  	APICanary                             string `json:"apiCanary"`
   270  	Canary                                string `json:"canary"`
   271  	CorrelationID                         string `json:"correlationId"`
   272  	SessionID                             string `json:"sessionId"`
   273  	Locale                                struct {
   274  		Mkt  string `json:"mkt"`
   275  		Lcid int    `json:"lcid"`
   276  	} `json:"locale"`
   277  	SlMaxRetry      int  `json:"slMaxRetry"`
   278  	SlReportFailure bool `json:"slReportFailure"`
   279  	Strings         struct {
   280  		Desktopsso struct {
   281  			Authenticatingmessage string `json:"authenticatingmessage"`
   282  		} `json:"desktopsso"`
   283  	} `json:"strings"`
   284  	Enums struct {
   285  		ClientMetricsModes struct {
   286  			None             int `json:"None"`
   287  			SubmitOnPost     int `json:"SubmitOnPost"`
   288  			SubmitOnRedirect int `json:"SubmitOnRedirect"`
   289  			InstrumentPlt    int `json:"InstrumentPlt"`
   290  		} `json:"ClientMetricsModes"`
   291  	} `json:"enums"`
   292  	Urls struct {
   293  		Instr struct {
   294  			Pageload   string `json:"pageload"`
   295  			Dssostatus string `json:"dssostatus"`
   296  		} `json:"instr"`
   297  	} `json:"urls"`
   298  	Browser struct {
   299  		Ltr     int `json:"ltr"`
   300  		Other   int `json:"_Other"`
   301  		Full    int `json:"Full"`
   302  		REOther int `json:"RE_Other"`
   303  		B       struct {
   304  			Name  string `json:"name"`
   305  			Major int    `json:"major"`
   306  			Minor int    `json:"minor"`
   307  		} `json:"b"`
   308  		Os struct {
   309  			Name    string `json:"name"`
   310  			Version string `json:"version"`
   311  		} `json:"os"`
   312  		V int `json:"V"`
   313  	} `json:"browser"`
   314  	Watson struct {
   315  		URL              string   `json:"url"`
   316  		Bundle           string   `json:"bundle"`
   317  		Sbundle          string   `json:"sbundle"`
   318  		Fbundle          string   `json:"fbundle"`
   319  		ResetErrorPeriod int      `json:"resetErrorPeriod"`
   320  		MaxCorsErrors    int      `json:"maxCorsErrors"`
   321  		MaxInjectErrors  int      `json:"maxInjectErrors"`
   322  		MaxErrors        int      `json:"maxErrors"`
   323  		MaxTotalErrors   int      `json:"maxTotalErrors"`
   324  		ExpSrcs          []string `json:"expSrcs"`
   325  		EnvErrorRedirect bool     `json:"envErrorRedirect"`
   326  		EnvErrorURL      string   `json:"envErrorUrl"`
   327  	} `json:"watson"`
   328  	Loader struct {
   329  		CdnRoots []string `json:"cdnRoots"`
   330  	} `json:"loader"`
   331  	ServerDetails struct {
   332  		Slc string `json:"slc"`
   333  		Dc  string `json:"dc"`
   334  		Ri  string `json:"ri"`
   335  		Ver struct {
   336  			V []int `json:"v"`
   337  		} `json:"ver"`
   338  		Rt string `json:"rt"`
   339  		Et int    `json:"et"`
   340  	} `json:"serverDetails"`
   341  	Country                    string `json:"country"`
   342  	FBreakBrandingSigninString bool   `json:"fBreakBrandingSigninString"`
   343  	URLNoCookies               string `json:"urlNoCookies"`
   344  	FTrimChromeBssoURL         bool   `json:"fTrimChromeBssoUrl"`
   345  }
   346  
   347  // Autogenerated skip mfa login response
   348  type SkipMfaResponse struct {
   349  	URLPostRedirect                     string `json:"urlPostRedirect"`
   350  	URLSkipMfaRegistration              string `json:"urlSkipMfaRegistration"`
   351  	URLMoreInfo                         string `json:"urlMoreInfo"`
   352  	SProofUpToken                       string `json:"sProofUpToken"`
   353  	SProofUpTokenName                   string `json:"sProofUpTokenName"`
   354  	SProofUpAuthState                   string `json:"sProofUpAuthState"`
   355  	SCanaryToken                        string `json:"sCanaryToken"`
   356  	IRemainingDaysToSkipMfaRegistration int    `json:"iRemainingDaysToSkipMfaRegistration"`
   357  	IMaxStackForKnockoutAsyncComponents int    `json:"iMaxStackForKnockoutAsyncComponents"`
   358  	StrCopyrightTxt                     string `json:"strCopyrightTxt"`
   359  	FShowButtons                        bool   `json:"fShowButtons"`
   360  	URLCdn                              string `json:"urlCdn"`
   361  	URLFooterTOU                        string `json:"urlFooterTOU"`
   362  	URLFooterPrivacy                    string `json:"urlFooterPrivacy"`
   363  	URLPost                             string `json:"urlPost"`
   364  	URLCancel                           string `json:"urlCancel"`
   365  	IPawnIcon                           int    `json:"iPawnIcon"`
   366  	SPOSTUsername                       string `json:"sPOST_Username"`
   367  	SFT                                 string `json:"sFT"`
   368  	SFTName                             string `json:"sFTName"`
   369  	SCanaryTokenName                    string `json:"sCanaryTokenName"`
   370  	DynamicTenantBranding               []struct {
   371  		Locale                 int    `json:"Locale"`
   372  		Illustration           string `json:"Illustration"`
   373  		UserIDLabel            string `json:"UserIdLabel"`
   374  		KeepMeSignedInDisabled bool   `json:"KeepMeSignedInDisabled"`
   375  		UseTransparentLightBox bool   `json:"UseTransparentLightBox"`
   376  	} `json:"dynamicTenantBranding"`
   377  	OAppCobranding struct {
   378  	} `json:"oAppCobranding"`
   379  	IBackgroundImage                      int    `json:"iBackgroundImage"`
   380  	FUseConstantPolling                   bool   `json:"fUseConstantPolling"`
   381  	FUseFlowTokenAsCanary                 bool   `json:"fUseFlowTokenAsCanary"`
   382  	FApplicationInsightsEnabled           bool   `json:"fApplicationInsightsEnabled"`
   383  	IApplicationInsightsEnabledPercentage int    `json:"iApplicationInsightsEnabledPercentage"`
   384  	URLSetDebugMode                       string `json:"urlSetDebugMode"`
   385  	FEnableCSSAnimation                   bool   `json:"fEnableCssAnimation"`
   386  	FAllowGrayOutLightBox                 bool   `json:"fAllowGrayOutLightBox"`
   387  	FIsRemoteNGCSupported                 bool   `json:"fIsRemoteNGCSupported"`
   388  	Scid                                  int    `json:"scid"`
   389  	Hpgact                                int    `json:"hpgact"`
   390  	Hpgid                                 int    `json:"hpgid"`
   391  	Pgid                                  string `json:"pgid"`
   392  	APICanary                             string `json:"apiCanary"`
   393  	Canary                                string `json:"canary"`
   394  	CorrelationID                         string `json:"correlationId"`
   395  	SessionID                             string `json:"sessionId"`
   396  	Locale                                struct {
   397  		Mkt  string `json:"mkt"`
   398  		Lcid int    `json:"lcid"`
   399  	} `json:"locale"`
   400  	SlMaxRetry      int  `json:"slMaxRetry"`
   401  	SlReportFailure bool `json:"slReportFailure"`
   402  	Strings         struct {
   403  		Desktopsso struct {
   404  			Authenticatingmessage string `json:"authenticatingmessage"`
   405  		} `json:"desktopsso"`
   406  	} `json:"strings"`
   407  	Enums struct {
   408  		ClientMetricsModes struct {
   409  			None             int `json:"None"`
   410  			SubmitOnPost     int `json:"SubmitOnPost"`
   411  			SubmitOnRedirect int `json:"SubmitOnRedirect"`
   412  			InstrumentPlt    int `json:"InstrumentPlt"`
   413  		} `json:"ClientMetricsModes"`
   414  	} `json:"enums"`
   415  	Urls struct {
   416  		Instr struct {
   417  			Pageload   string `json:"pageload"`
   418  			Dssostatus string `json:"dssostatus"`
   419  		} `json:"instr"`
   420  	} `json:"urls"`
   421  	Browser struct {
   422  		Ltr     int `json:"ltr"`
   423  		Other   int `json:"_Other"`
   424  		Full    int `json:"Full"`
   425  		REOther int `json:"RE_Other"`
   426  		B       struct {
   427  			Name  string `json:"name"`
   428  			Major int    `json:"major"`
   429  			Minor int    `json:"minor"`
   430  		} `json:"b"`
   431  		Os struct {
   432  			Name    string `json:"name"`
   433  			Version string `json:"version"`
   434  		} `json:"os"`
   435  		V int `json:"V"`
   436  	} `json:"browser"`
   437  	Watson struct {
   438  		URL              string   `json:"url"`
   439  		Bundle           string   `json:"bundle"`
   440  		Sbundle          string   `json:"sbundle"`
   441  		Fbundle          string   `json:"fbundle"`
   442  		ResetErrorPeriod int      `json:"resetErrorPeriod"`
   443  		MaxCorsErrors    int      `json:"maxCorsErrors"`
   444  		MaxInjectErrors  int      `json:"maxInjectErrors"`
   445  		MaxErrors        int      `json:"maxErrors"`
   446  		MaxTotalErrors   int      `json:"maxTotalErrors"`
   447  		ExpSrcs          []string `json:"expSrcs"`
   448  		EnvErrorRedirect bool     `json:"envErrorRedirect"`
   449  		EnvErrorURL      string   `json:"envErrorUrl"`
   450  	} `json:"watson"`
   451  	Loader struct {
   452  		CdnRoots []string `json:"cdnRoots"`
   453  	} `json:"loader"`
   454  	ServerDetails struct {
   455  		Slc string `json:"slc"`
   456  		Dc  string `json:"dc"`
   457  		Ri  string `json:"ri"`
   458  		Ver struct {
   459  			V []int `json:"v"`
   460  		} `json:"ver"`
   461  		Rt string `json:"rt"`
   462  		Et int    `json:"et"`
   463  	} `json:"serverDetails"`
   464  	Country                    string `json:"country"`
   465  	FBreakBrandingSigninString bool   `json:"fBreakBrandingSigninString"`
   466  	URLNoCookies               string `json:"urlNoCookies"`
   467  	FTrimChromeBssoURL         bool   `json:"fTrimChromeBssoUrl"`
   468  }
   469  
   470  // mfa request
   471  type mfaRequest struct {
   472  	AuthMethodID       string `json:"AuthMethodId"`
   473  	Method             string `json:"Method"`
   474  	Ctx                string `json:"Ctx"`
   475  	FlowToken          string `json:"FlowToken"`
   476  	SessionID          string `json:"SessionId,omitempty"`
   477  	AdditionalAuthData string `json:"AdditionalAuthData,omitempty"`
   478  }
   479  
   480  // mfa response
   481  type mfaResponse struct {
   482  	Success       bool        `json:"Success"`
   483  	ResultValue   string      `json:"ResultValue"`
   484  	Message       interface{} `json:"Message"`
   485  	AuthMethodID  string      `json:"AuthMethodId"`
   486  	ErrCode       int         `json:"ErrCode"`
   487  	Retry         bool        `json:"Retry"`
   488  	FlowToken     string      `json:"FlowToken"`
   489  	Ctx           string      `json:"Ctx"`
   490  	SessionID     string      `json:"SessionId"`
   491  	CorrelationID string      `json:"CorrelationId"`
   492  	Timestamp     time.Time   `json:"Timestamp"`
   493  }
   494  
   495  // Autogenerate ProcessAuth response
   496  // some case, some fiels is not exists
   497  type processAuthResponse struct {
   498  	IMaxStackForKnockoutAsyncComponents int    `json:"iMaxStackForKnockoutAsyncComponents"`
   499  	StrCopyrightTxt                     string `json:"strCopyrightTxt"`
   500  	FShowButtons                        bool   `json:"fShowButtons"`
   501  	URLCdn                              string `json:"urlCdn"`
   502  	URLFooterTOU                        string `json:"urlFooterTOU"`
   503  	URLFooterPrivacy                    string `json:"urlFooterPrivacy"`
   504  	URLPost                             string `json:"urlPost"`
   505  	IPawnIcon                           int    `json:"iPawnIcon"`
   506  	SPOSTUsername                       string `json:"sPOST_Username"`
   507  	SFT                                 string `json:"sFT"`
   508  	SFTName                             string `json:"sFTName"`
   509  	SCtx                                string `json:"sCtx"`
   510  	SCanaryTokenName                    string `json:"sCanaryTokenName"`
   511  	DynamicTenantBranding               []struct {
   512  		Locale                 int    `json:"Locale"`
   513  		Illustration           string `json:"Illustration"`
   514  		UserIDLabel            string `json:"UserIdLabel"`
   515  		KeepMeSignedInDisabled bool   `json:"KeepMeSignedInDisabled"`
   516  		UseTransparentLightBox bool   `json:"UseTransparentLightBox"`
   517  	} `json:"dynamicTenantBranding"`
   518  	OAppCobranding struct {
   519  	} `json:"oAppCobranding"`
   520  	IBackgroundImage                      int    `json:"iBackgroundImage"`
   521  	FUseConstantPolling                   bool   `json:"fUseConstantPolling"`
   522  	FUseFlowTokenAsCanary                 bool   `json:"fUseFlowTokenAsCanary"`
   523  	FApplicationInsightsEnabled           bool   `json:"fApplicationInsightsEnabled"`
   524  	IApplicationInsightsEnabledPercentage int    `json:"iApplicationInsightsEnabledPercentage"`
   525  	URLSetDebugMode                       string `json:"urlSetDebugMode"`
   526  	FEnableCSSAnimation                   bool   `json:"fEnableCssAnimation"`
   527  	FAllowGrayOutLightBox                 bool   `json:"fAllowGrayOutLightBox"`
   528  	FIsRemoteNGCSupported                 bool   `json:"fIsRemoteNGCSupported"`
   529  	Scid                                  int    `json:"scid"`
   530  	Hpgact                                int    `json:"hpgact"`
   531  	Hpgid                                 int    `json:"hpgid"`
   532  	Pgid                                  string `json:"pgid"`
   533  	APICanary                             string `json:"apiCanary"`
   534  	Canary                                string `json:"canary"`
   535  	CorrelationID                         string `json:"correlationId"`
   536  	SessionID                             string `json:"sessionId"`
   537  	Locale                                struct {
   538  		Mkt  string `json:"mkt"`
   539  		Lcid int    `json:"lcid"`
   540  	} `json:"locale"`
   541  	SlMaxRetry      int  `json:"slMaxRetry"`
   542  	SlReportFailure bool `json:"slReportFailure"`
   543  	Strings         struct {
   544  		Desktopsso struct {
   545  			Authenticatingmessage string `json:"authenticatingmessage"`
   546  		} `json:"desktopsso"`
   547  	} `json:"strings"`
   548  	Enums struct {
   549  		ClientMetricsModes struct {
   550  			None             int `json:"None"`
   551  			SubmitOnPost     int `json:"SubmitOnPost"`
   552  			SubmitOnRedirect int `json:"SubmitOnRedirect"`
   553  			InstrumentPlt    int `json:"InstrumentPlt"`
   554  		} `json:"ClientMetricsModes"`
   555  	} `json:"enums"`
   556  	Urls struct {
   557  		Instr struct {
   558  			Pageload   string `json:"pageload"`
   559  			Dssostatus string `json:"dssostatus"`
   560  		} `json:"instr"`
   561  	} `json:"urls"`
   562  	Browser struct {
   563  		Ltr     int `json:"ltr"`
   564  		Other   int `json:"_Other"`
   565  		Full    int `json:"Full"`
   566  		REOther int `json:"RE_Other"`
   567  		B       struct {
   568  			Name  string `json:"name"`
   569  			Major int    `json:"major"`
   570  			Minor int    `json:"minor"`
   571  		} `json:"b"`
   572  		Os struct {
   573  			Name    string `json:"name"`
   574  			Version string `json:"version"`
   575  		} `json:"os"`
   576  		V int `json:"V"`
   577  	} `json:"browser"`
   578  	Watson struct {
   579  		URL              string   `json:"url"`
   580  		Bundle           string   `json:"bundle"`
   581  		Sbundle          string   `json:"sbundle"`
   582  		Fbundle          string   `json:"fbundle"`
   583  		ResetErrorPeriod int      `json:"resetErrorPeriod"`
   584  		MaxCorsErrors    int      `json:"maxCorsErrors"`
   585  		MaxInjectErrors  int      `json:"maxInjectErrors"`
   586  		MaxErrors        int      `json:"maxErrors"`
   587  		MaxTotalErrors   int      `json:"maxTotalErrors"`
   588  		ExpSrcs          []string `json:"expSrcs"`
   589  		EnvErrorRedirect bool     `json:"envErrorRedirect"`
   590  		EnvErrorURL      string   `json:"envErrorUrl"`
   591  	} `json:"watson"`
   592  	Loader struct {
   593  		CdnRoots []string `json:"cdnRoots"`
   594  	} `json:"loader"`
   595  	ServerDetails struct {
   596  		Slc string `json:"slc"`
   597  		Dc  string `json:"dc"`
   598  		Ri  string `json:"ri"`
   599  		Ver struct {
   600  			V []int `json:"v"`
   601  		} `json:"ver"`
   602  		Rt string `json:"rt"`
   603  		Et int    `json:"et"`
   604  	} `json:"serverDetails"`
   605  	Country                    string `json:"country"`
   606  	FBreakBrandingSigninString bool   `json:"fBreakBrandingSigninString"`
   607  	URLNoCookies               string `json:"urlNoCookies"`
   608  	FTrimChromeBssoURL         bool   `json:"fTrimChromeBssoUrl"`
   609  }
   610  
   611  // New create a new AzureAD client
   612  func New(idpAccount *cfg.IDPAccount) (*Client, error) {
   613  
   614  	tr := &http.Transport{
   615  		Proxy:           http.ProxyFromEnvironment,
   616  		TLSClientConfig: &tls.Config{InsecureSkipVerify: idpAccount.SkipVerify, Renegotiation: tls.RenegotiateFreelyAsClient},
   617  	}
   618  
   619  	client, err := provider.NewHTTPClient(tr)
   620  	if err != nil {
   621  		return nil, errors.Wrap(err, "error building http client")
   622  	}
   623  
   624  	return &Client{
   625  		client:     client,
   626  		idpAccount: idpAccount,
   627  	}, nil
   628  }
   629  
   630  // Authenticate to AzureAD and return the data from the body of the SAML assertion.
   631  func (ac *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) {
   632  
   633  	var samlAssertion string
   634  	var res *http.Response
   635  
   636  	// idpAccount.URL = https://account.activedirectory.windowsazure.com
   637  
   638  	// startSAML
   639  	startURL := fmt.Sprintf("%s/applications/redirecttofederatedapplication.aspx?Operation=LinkedSignIn&applicationId=%s", ac.idpAccount.URL, ac.idpAccount.AppID)
   640  
   641  	res, err := ac.client.Get(startURL)
   642  	if err != nil {
   643  		return samlAssertion, errors.Wrap(err, "error retrieving form")
   644  	}
   645  
   646  	// data is embeded javascript object
   647  	// <script><![CDATA[  $Config=......; ]]>
   648  	scanner := bufio.NewScanner(res.Body)
   649  	var startSAMLJson string
   650  	for scanner.Scan() {
   651  		scanLine := strings.TrimSpace(scanner.Text())
   652  		if strings.Contains(scanLine, "$Config") {
   653  			startSAMLJson = scanLine[strings.Index(scanLine, "$Config=")+8 : strings.LastIndex(scanLine, ";")]
   654  			break
   655  		}
   656  	}
   657  	var startSAMLResp startSAMLResponse
   658  	if err := json.Unmarshal([]byte(startSAMLJson), &startSAMLResp); err != nil {
   659  		return samlAssertion, errors.Wrap(err, "startSAML response unmarshal error")
   660  	}
   661  
   662  	// password login
   663  	loginValues := url.Values{}
   664  	loginValues.Set(startSAMLResp.SFTName, startSAMLResp.SFT)
   665  	loginValues.Set("ctx", startSAMLResp.SCtx)
   666  	loginValues.Set("login", loginDetails.Username)
   667  	loginValues.Set("passwd", loginDetails.Password)
   668  	passwordLoginRequest, err := http.NewRequest("POST", startSAMLResp.URLPost, strings.NewReader(loginValues.Encode()))
   669  	if err != nil {
   670  		return samlAssertion, errors.Wrap(err, "error retrieving login results")
   671  	}
   672  	passwordLoginRequest.Header.Add("Content-Type", "application/x-www-form-urlencoded")
   673  	res, err = ac.client.Do(passwordLoginRequest)
   674  	if err != nil {
   675  		return samlAssertion, errors.Wrap(err, "error retrieving login results")
   676  	}
   677  	// data is embeded javascript object
   678  	// <script><![CDATA[  $Config=......; ]]>
   679  	scanner = bufio.NewScanner(res.Body)
   680  	var loginPasswordJson string
   681  	for scanner.Scan() {
   682  		scanLine := strings.TrimSpace(scanner.Text())
   683  		if strings.Contains(scanLine, "$Config") {
   684  			loginPasswordJson = scanLine[strings.Index(scanLine, "$Config=")+8 : strings.LastIndex(scanLine, ";")]
   685  			break
   686  		}
   687  	}
   688  	var loginPasswordResp passwordLoginResponse
   689  	var loginPasswordSkipMfaResp SkipMfaResponse
   690  	if err := json.Unmarshal([]byte(loginPasswordJson), &loginPasswordResp); err != nil {
   691  		return samlAssertion, errors.Wrap(err, "loginPassword response unmarshal error")
   692  	}
   693  	if err := json.Unmarshal([]byte(loginPasswordJson), &loginPasswordSkipMfaResp); err != nil {
   694  		return samlAssertion, errors.Wrap(err, "loginPassword response unmarshal error")
   695  	}
   696  	var restartSAMLResp startSAMLResponse
   697  	if err := json.Unmarshal([]byte(loginPasswordJson), &restartSAMLResp); err != nil {
   698  		return samlAssertion, errors.Wrap(err, "startSAML response unmarshal error")
   699  	}
   700    if restartSAMLResp.URLGitHubFed != "" {
   701  			return samlAssertion, errors.Wrap(err, "login failed")
   702    }
   703  
   704  	// skip mfa
   705  	if loginPasswordSkipMfaResp.URLSkipMfaRegistration != "" {
   706  		res, err = ac.client.Get(loginPasswordSkipMfaResp.URLSkipMfaRegistration)
   707  		if err != nil {
   708  			return samlAssertion, errors.Wrap(err, "error retrieving skip mfa results")
   709  		}
   710  	} else {
   711  
   712  		// start mfa
   713  		mfas := loginPasswordResp.ArrUserProofs
   714  		if len(mfas) == 0 {
   715  			return samlAssertion, errors.Wrap(err, "mfa not found")
   716  		}
   717  		mfa := mfas[0]
   718  		switch ac.idpAccount.MFA {
   719  
   720  		case "Auto":
   721  			for _, v := range mfas {
   722  				if v.IsDefault {
   723  					mfa = v
   724  					break
   725  				}
   726  			}
   727  		default:
   728  			for _, v := range mfas {
   729  				if v.AuthMethodID == ac.idpAccount.MFA {
   730  					mfa = v
   731  					break
   732  				}
   733  			}
   734  		}
   735  		mfaReq := mfaRequest{AuthMethodID: mfa.AuthMethodID, Method: "BeginAuth", Ctx: loginPasswordResp.SCtx, FlowToken: loginPasswordResp.SFT}
   736  		mfaReqJson, err := json.Marshal(mfaReq)
   737  		if err != nil {
   738  			return samlAssertion, err
   739  		}
   740  		mfaBeginRequest, err := http.NewRequest("POST", loginPasswordResp.URLBeginAuth, strings.NewReader(string(mfaReqJson)))
   741  		if err != nil {
   742  			return samlAssertion, errors.Wrap(err, "error retrieving begin mfa")
   743  		}
   744  		mfaBeginRequest.Header.Add("Content-Type", "application/json")
   745  		res, err = ac.client.Do(mfaBeginRequest)
   746  		if err != nil {
   747  			return samlAssertion, errors.Wrap(err, "error retrieving begin mfa")
   748  		}
   749  		mfaBeginJson := make([]byte, res.ContentLength, res.ContentLength)
   750  		if n, err := res.Body.Read(mfaBeginJson); err != nil && err != io.EOF || n != int(res.ContentLength) {
   751  			return samlAssertion, errors.Wrap(err, "mfa BeginAuth response error")
   752  		}
   753  		var mfaResp mfaResponse
   754  		if err := json.Unmarshal(mfaBeginJson, &mfaResp); err != nil {
   755  			return samlAssertion, errors.Wrap(err, "mfa BeginAuth  response unmarshal error")
   756  		}
   757  		if !mfaResp.Success {
   758  			return samlAssertion, fmt.Errorf("mfa BeginAuth is not success %v", mfaResp.Message)
   759  		}
   760  
   761  		//  mfa end
   762      for i:=0;; i++{
   763  			mfaReq = mfaRequest{
   764  				AuthMethodID: mfaResp.AuthMethodID,
   765  				Method:       "EndAuth",
   766  				Ctx:          mfaResp.Ctx,
   767  				FlowToken:    mfaResp.FlowToken,
   768  				SessionID:    mfaResp.SessionID,
   769  			}
   770  			if mfaReq.AuthMethodID == "PhoneAppOTP" || mfaReq.AuthMethodID == "OneWaySMS" {
   771  				verifyCode := prompter.StringRequired("Enter verification code")
   772  				mfaReq.AdditionalAuthData = verifyCode
   773  			}
   774        if mfaReq.AuthMethodID == "PhoneAppNotification" && i==0 {
   775          fmt.Println("Phone approval required.")
   776        }
   777  			mfaReqJson, err := json.Marshal(mfaReq)
   778  			if err != nil {
   779  				return samlAssertion, err
   780  			}
   781  			mfaEndRequest, err := http.NewRequest("POST", loginPasswordResp.URLEndAuth, strings.NewReader(string(mfaReqJson)))
   782  			if err != nil {
   783  				return samlAssertion, errors.Wrap(err, "error retrieving begin mfa")
   784  			}
   785  			mfaEndRequest.Header.Add("Content-Type", "application/json")
   786  			res, err = ac.client.Do(mfaEndRequest)
   787  			if err != nil {
   788  				return samlAssertion, errors.Wrap(err, "error retrieving begin mfa")
   789  			}
   790  			mfaJson := make([]byte, res.ContentLength, res.ContentLength)
   791  			if n, err := res.Body.Read(mfaJson); err != nil && err != io.EOF || n != int(res.ContentLength) {
   792  				return samlAssertion, errors.Wrap(err, "mfa EndAuth response error")
   793  			}
   794  			if err := json.Unmarshal(mfaJson, &mfaResp); err != nil {
   795  				return samlAssertion, errors.Wrap(err, "mfa EndAuth  response unmarshal error")
   796  			}
   797  			if mfaResp.ErrCode != 0 {
   798  				return samlAssertion, fmt.Errorf("error mfa fail errcode: %d, message: %v", mfaResp.ErrCode, mfaResp.Message)
   799  			}
   800  			if mfaResp.Success {
   801  				break
   802  			}
   803  			if !mfaResp.Retry {
   804  				break
   805  			}
   806  			// if mfaResp.Retry == true then
   807  			// must exist loginPasswordResp.OPerAuthPollingInterval[mfaResp.AuthMethodID]
   808  			time.Sleep(time.Duration(loginPasswordResp.OPerAuthPollingInterval[mfaResp.AuthMethodID]) * time.Second)
   809  		}
   810  		if !mfaResp.Success {
   811  			return samlAssertion, fmt.Errorf("error mfa fail")
   812  		}
   813  
   814  		// ProcessAuth
   815  		ProcessAuthValues := url.Values{}
   816  		ProcessAuthValues.Set(startSAMLResp.SFTName, mfaResp.FlowToken)
   817  		ProcessAuthValues.Set("request", mfaResp.Ctx)
   818  		ProcessAuthValues.Set("login", loginDetails.Username)
   819  
   820  		ProcessAuthRequest, err := http.NewRequest("POST", loginPasswordResp.URLPost, strings.NewReader(ProcessAuthValues.Encode()))
   821  		if err != nil {
   822  			return samlAssertion, errors.Wrap(err, "error retrieving process auth results")
   823  		}
   824  		ProcessAuthRequest.Header.Add("Content-Type", "application/x-www-form-urlencoded")
   825  		res, err = ac.client.Do(ProcessAuthRequest)
   826  		if err != nil {
   827  			return samlAssertion, errors.Wrap(err, "error retrieving process auth results")
   828  		}
   829  		// data is embeded javascript object
   830  		// <script><![CDATA[  $Config=......; ]]>
   831  		scanner = bufio.NewScanner(res.Body)
   832  		var ProcessAuthJson string
   833  		for scanner.Scan() {
   834  			scanLine := strings.TrimSpace(scanner.Text())
   835  			if strings.Contains(scanLine, "$Config") {
   836  				ProcessAuthJson = scanLine[strings.Index(scanLine, "$Config=")+8 : strings.LastIndex(scanLine, ";")]
   837  				break
   838  			}
   839  		}
   840  		var processAuthResp processAuthResponse
   841  		if err := json.Unmarshal([]byte(ProcessAuthJson), &processAuthResp); err != nil {
   842  			return samlAssertion, errors.Wrap(err, "ProcessAuth response unmarshal error")
   843  		}
   844  
   845  		// kmsi
   846  		KmsiURL := res.Request.URL.Scheme + "://" + res.Request.URL.Host + processAuthResp.URLPost
   847  		KmsiValues := url.Values{}
   848  		KmsiValues.Set("flowToken", processAuthResp.SFT)
   849  		KmsiValues.Set("ctx", processAuthResp.SCtx)
   850  
   851  		KmsiRequest, err := http.NewRequest("POST", KmsiURL, strings.NewReader(KmsiValues.Encode()))
   852  		if err != nil {
   853  			return samlAssertion, errors.Wrap(err, "error retrieving kmsi results")
   854  		}
   855  		KmsiRequest.Header.Add("Content-Type", "application/x-www-form-urlencoded")
   856  		ac.client.DisableFollowRedirect()
   857  		res, err = ac.client.Do(KmsiRequest)
   858  		if err != nil {
   859  			return samlAssertion, errors.Wrap(err, "error retrieving kmsi results")
   860  		}
   861  		ac.client.EnableFollowRedirect()
   862  	}
   863  
   864  	//  oidc
   865  	doc, err := goquery.NewDocumentFromResponse(res)
   866  	if err != nil {
   867  		return samlAssertion, errors.Wrap(err, "failed to build document from response")
   868  	}
   869  	// data in input tag
   870  	authForm := url.Values{}
   871  	var authSubmitURL string
   872  
   873  	doc.Find("input").Each(func(i int, s *goquery.Selection) {
   874  		name, ok := s.Attr("name")
   875  		if !ok {
   876  			return
   877  		}
   878  		value, ok := s.Attr("value")
   879  		if !ok {
   880  			return
   881  		}
   882  		authForm.Set(name, value)
   883  	})
   884  
   885  	doc.Find("form").Each(func(i int, s *goquery.Selection) {
   886  		action, ok := s.Attr("action")
   887  		if !ok {
   888  			return
   889  		}
   890  		authSubmitURL = action
   891  	})
   892  
   893  	if authSubmitURL == "" {
   894  		return samlAssertion, fmt.Errorf("unable to locate IDP oidc form submit URL")
   895  	}
   896  
   897  	req, err := http.NewRequest("POST", authSubmitURL, strings.NewReader(authForm.Encode()))
   898  	if err != nil {
   899  		return samlAssertion, errors.Wrap(err, "error building authentication request")
   900  	}
   901  
   902  	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
   903  
   904  	ac.client.EnableFollowRedirect()
   905  	res, err = ac.client.Do(req)
   906  	if err != nil {
   907  		return samlAssertion, errors.Wrap(err, "error retrieving oidc login form results")
   908  	}
   909  
   910  	//  get saml assertion
   911  	oidcResponse, err := ioutil.ReadAll(res.Body)
   912  	if err != nil {
   913  		return samlAssertion, errors.Wrap(err, "oidc login response error")
   914  	}
   915  
   916  	oidcResponseStr := string(oidcResponse)
   917  
   918  	// data is embeded javascript
   919  	// window.location = 'https:/..../?SAMLRequest=......'
   920  	oidcResponseList := strings.Split(oidcResponseStr, ";")
   921  	var SAMLRequestURL string
   922  	for _, v := range oidcResponseList {
   923  		if strings.Contains(v, "SAMLRequest") {
   924  			startURLPos := strings.Index(v, "https://")
   925  			endURLPos := strings.Index(v[startURLPos:], "'")
   926  			if endURLPos == -1 {
   927  				endURLPos = strings.Index(v[startURLPos:], "\"")
   928  			}
   929  			SAMLRequestURL = v[startURLPos : startURLPos+endURLPos]
   930  		}
   931  
   932  	}
   933  	if SAMLRequestURL == "" {
   934  		return samlAssertion, fmt.Errorf("unable to locate SAMLRequest URL")
   935  	}
   936  
   937  	req, err = http.NewRequest("GET", SAMLRequestURL, nil)
   938  
   939  	res, err = ac.client.Do(req)
   940  	if err != nil {
   941  		return samlAssertion, errors.Wrap(err, "error retrieving oidc login form results")
   942  	}
   943  
   944  	// if mfa skipped then get $Config and urlSkipMfaRegistration
   945  	// get urlSkipMfaRegistraition to return saml assertion
   946  	resBody, err := ioutil.ReadAll(res.Body)
   947  	if err != nil {
   948  		return samlAssertion, errors.Wrap(err, "error oidc login response read")
   949  	}
   950  	resBodyStr := string(resBody)
   951  	if strings.Contains(resBodyStr, "urlSkipMfaRegistration") {
   952  		var samlAssertionSkipMfaResp SkipMfaResponse
   953  		var skipMfaJson string
   954  		responseList := strings.Split(resBodyStr, "<")
   955  		for _, line := range responseList {
   956  
   957  			if strings.Contains(line, "$Config") {
   958  				skipMfaJson = line[strings.Index(line, "$Config=")+8 : strings.LastIndex(line, ";")]
   959  				break
   960  			}
   961  		}
   962  		if err := json.Unmarshal([]byte(skipMfaJson), &samlAssertionSkipMfaResp); err != nil {
   963  			return samlAssertion, errors.Wrap(err, "SAMLAssertion skip mfa response unmarshal error")
   964  		}
   965  		res, err = ac.client.Get(samlAssertionSkipMfaResp.URLSkipMfaRegistration)
   966  		if err != nil {
   967  			return samlAssertion, errors.Wrap(err, "SAMLAssertion skip mfa url get  error")
   968  		}
   969  		resBody, err = ioutil.ReadAll(res.Body)
   970  		if err != nil {
   971  			return samlAssertion, errors.Wrap(err, "SAMLAssertion skip mfa request error")
   972  		}
   973  		resBodyStr = string(resBody)
   974  	}
   975  
   976  	// data in input tag
   977  	doc, err = goquery.NewDocumentFromReader(strings.NewReader(resBodyStr))
   978  	if err != nil {
   979  		return samlAssertion, errors.Wrap(err, "failed to build document from response")
   980  	}
   981  
   982  	doc.Find("input").Each(func(i int, s *goquery.Selection) {
   983  		attrName, ok := s.Attr("name")
   984  		if !ok {
   985  			return
   986  		}
   987  		if attrName == "SAMLResponse" {
   988  			samlAssertion, ok = s.Attr("value")
   989  			if !ok {
   990  				return
   991  			}
   992  		}
   993  	})
   994  	if samlAssertion == "" {
   995  		return samlAssertion, fmt.Errorf("failed get SAMLAssersion")
   996  	}
   997  	return samlAssertion, nil
   998  }