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 }