Skip to content

Commit

Permalink
Add support for Google SKOTP (security key based OTP) and CAPTCHA dis…
Browse files Browse the repository at this point in the history
…play via iTerm (if available)
  • Loading branch information
thecubed committed Sep 19, 2019
1 parent e988728 commit 9781c43
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 5 deletions.
2 changes: 1 addition & 1 deletion cmd/saml2aws/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func main() {
// Common (to all commands) settings
commonFlags := new(flags.CommonFlags)
app.Flag("idp-account", "The name of the configured IDP account. (env: SAML2AWS_IDP_ACCOUNT)").Envar("SAML2AWS_IDP_ACCOUNT").Short('a').Default("default").StringVar(&commonFlags.IdpAccount)
app.Flag("idp-provider", "The configured IDP provider. (env: SAML2AWS_IDP_PROVIDER)").Envar("SAML2AWS_IDP_PROVIDER").EnumVar(&commonFlags.IdpProvider, "AzureAD", "ADFS", "ADFS2", "Ping", "JumpCloud", "Okta", "OneLogin", "PSU", "KeyCloak", "F5APM", "Shibboleth")
app.Flag("idp-provider", "The configured IDP provider. (env: SAML2AWS_IDP_PROVIDER)").Envar("SAML2AWS_IDP_PROVIDER").EnumVar(&commonFlags.IdpProvider, "AzureAD", "ADFS", "ADFS2", "Ping", "JumpCloud", "Okta", "OneLogin", "PSU", "KeyCloak", "F5APM", "Shibboleth", "GoogleApps")
app.Flag("mfa", "The name of the mfa. (env: SAML2AWS_MFA)").Envar("SAML2AWS_MFA").StringVar(&commonFlags.MFA)
app.Flag("skip-verify", "Skip verification of server certificate. (env: SAML2AWS_SKIP_VERIFY)").Envar("SAML2AWS_SKIP_VERIFY").Short('s').BoolVar(&commonFlags.SkipVerify)
app.Flag("url", "The URL of the SAML IDP server used to login. (env: SAML2AWS_URL)").Envar("SAML2AWS_URL").StringVar(&commonFlags.URL)
Expand Down
58 changes: 54 additions & 4 deletions pkg/provider/googleapps/googleapps.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package googleapps

import (
"bytes"
b64 "encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"

"github.com/PuerkitoBio/goquery"
Expand Down Expand Up @@ -76,9 +79,10 @@ func (kc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error)
return "", errors.New("captcha image not found but requested")
}

fmt.Println("Open this link in a browser:\n", captchaPictureURL)

captcha := prompter.String("Captcha", "")
captcha, err := kc.tryDisplayCaptcha(captchaPictureURL)
if err != nil {
return "", err
}

captchaForm, captchaURL, err := extractInputsByFormID(responseDoc, "gaia_loginform")

Expand All @@ -103,6 +107,42 @@ func (kc *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error)
return samlAssertion, nil
}

func (kc *Client) tryDisplayCaptcha(captchaPictureURL string) (string, error) {
// TODO: check for user flag for easy captcha presentation

if os.Getenv("TERM_PROGRAM") == "iTerm.app" {
// Use iTerm to show the image if available
return kc.iTermCaptchaPrompt(captchaPictureURL)
} else {
return simpleCaptchaPrompt(captchaPictureURL), nil
}
}

func (kc *Client) iTermCaptchaPrompt(captchaPictureURL string) (string, error) {
fmt.Printf("Detected iTerm, displaying URL: %s\n", captchaPictureURL)
imgResp, err := kc.client.Get(captchaPictureURL)
if err != nil {
return "", errors.Wrap(err, "unable to fetch captcha image")
}
var buf bytes.Buffer
b64Encoder := b64.NewEncoder(b64.StdEncoding, &buf)
_, _ = io.Copy(b64Encoder, imgResp.Body)
_ = b64Encoder.Close()

if os.Getenv("TERM") == "screen" {
fmt.Println("Detected tmux, using specific workaround...")
fmt.Printf("\033Ptmux;\033\033]1337;File=width=40;preserveAspectRatio=1;inline=1;:%s\a\033\\\n", buf.String())
} else {
fmt.Printf("\033]1337;File=width=40;preserveAspectRatio=1;inline=1;:%s\a\n", buf.String())
}
return prompter.String("Captcha", ""), nil
}

func simpleCaptchaPrompt(captchaPictureURL string) string {
fmt.Println("Open this link in a browser:\n", captchaPictureURL)
return prompter.String("Captcha", "")
}

func (kc *Client) loadFirstPage(loginDetails *creds.LoginDetails) (string, url.Values, error) {

req, err := http.NewRequest("GET", loginDetails.URL+"&hl=en&loc=US", nil)
Expand Down Expand Up @@ -267,6 +307,15 @@ func (kc *Client) loadChallengePage(submitURL string, referer string, authForm u
// responseForm.Set("Pin", token)
responseForm.Set("TrustDevice", "on") // Don't ask again on this computer

return kc.loadResponsePage(u.String(), submitURL, responseForm)

case strings.Contains(secondActionURL, "challenge/skotp/"): // handle one-time HOTP challenge
fmt.Println("Get a one-time code by visiting https://g.co/sc on another device where you can use your security key")
var token = prompter.RequestSecurityCode("000 000")

responseForm.Set("Pin", token)
responseForm.Set("TrustDevice", "on") // Don't ask again on this computer

return kc.loadResponsePage(u.String(), submitURL, responseForm)
}

Expand Down Expand Up @@ -321,7 +370,8 @@ func (kc *Client) loadAlternateChallengePage(submitURL string, referer string, a

if strings.Contains(action, "challenge/totp/") ||
strings.Contains(action, "challenge/ipp/") ||
strings.Contains(action, "challenge/az/") {
strings.Contains(action, "challenge/az/") ||
strings.Contains(action, "challenge/skotp/"){

challengeEntry, _ = s.Attr("data-challengeentry")
return false
Expand Down

0 comments on commit 9781c43

Please sign in to comment.