From cf4f62cccbbe1422eb55fd23f73894d4a5a2c688 Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 25 Oct 2020 21:34:01 +0100 Subject: [PATCH] Fix Audi authentication and align with VW --- vehicle/audi.go | 81 +++++++----------------------------------- vehicle/vw.go | 56 ++++++----------------------- vehicle/vw/identity.go | 20 +++++++++-- 3 files changed, 40 insertions(+), 117 deletions(-) diff --git a/vehicle/audi.go b/vehicle/audi.go index 054293741..169a664f5 100644 --- a/vehicle/audi.go +++ b/vehicle/audi.go @@ -1,15 +1,10 @@ package vehicle import ( - "crypto/hmac" - "crypto/sha256" - "encoding/hex" "errors" - "fmt" "net/http" "net/http/cookiejar" "net/url" - "strconv" "strings" "time" @@ -36,10 +31,7 @@ func init() { registry.Add("audi", NewAudiFromConfig) } -// AudiHashSecret is used for obtaining the X-QMauth header hash value from the current timestamp -var AudiHashSecret = "not contained in repo due to legal concerns" - -const audiOAuthClientID = "77869e21-e30a-4a92-b016-48ab7d3db1d8" +const audiClientID = "77869e21-e30a-4a92-b016-48ab7d3db1d8" // NewAudiFromConfig creates a new vehicle func NewAudiFromConfig(other map[string]interface{}) (api.Vehicle, error) { @@ -91,88 +83,39 @@ func NewAudiFromConfig(other map[string]interface{}) (api.Vehicle, error) { } func (v *Audi) authFlow() error { - var err error - var uri string var req *http.Request - var resp *http.Response - var tokens vw.Tokens const clientID = "09b6cbec-cd19-4589-82fd-363dfa8c24da@apps_vw-dilab_com" - const clientIDAlias = "934928ef" // "09b6cbec-cd19-4589-82fd-363dfa8c24da@apps_vw-dilab_com" const redirectURI = "myaudi:///" query := url.Values(map[string][]string{ - "response_type": {"code"}, + "response_type": {"id_token token"}, "client_id": {clientID}, "redirect_uri": {redirectURI}, - "scope": {"address profile badge birthdate birthplace nationalIdentifier nationality profession email vin phone nickname name picture mbb gallery openid"}, - "state": {"7f8260b5-682f-4db8-b171-50a5189a1c08"}, - "nonce": {"7f8260b5-682f-4db8-b171-50a5189a1c08"}, + "scope": {"openid profile mbb vin badge birthdate nickname email address phone name picture"}, + "state": {vw.RandomString(43)}, + "nonce": {vw.RandomString(43)}, "prompt": {"login"}, "ui_locales": {"de-DE"}, }) - uri = "https://identity.vwgroup.io/oidc/v1/authorize?" + query.Encode() - if err == nil { - identity := &vw.Identity{Client: v.Client} - resp, err = identity.Login(uri, v.user, v.password) - } - - if err == nil { - var code string - if location, err := url.Parse(resp.Header.Get("Location")); err == nil { - code = location.Query().Get("code") - } - - data := url.Values(map[string][]string{ - "client_id": {clientID}, - "grant_type": {"authorization_code"}, - "code": {code}, - "redirect_uri": {redirectURI}, - "response_type": {"token id_token"}, - }) - - var hash string - secret, err := hex.DecodeString(AudiHashSecret) - if err == nil { - // timestamp rounded to 100s precision - ts := strconv.FormatInt(time.Now().Unix()/100, 10) - - mac := hmac.New(sha256.New, secret) - _, err = mac.Write([]byte(ts)) - - hash = fmt.Sprintf("v1:%s:%0x", clientIDAlias, mac.Sum(nil)) - } - - if err == nil { - uri = "https://app-api.my.audi.com/myaudiappidk/v1/emea/token" - req, err = request.New(http.MethodPost, uri, strings.NewReader(data.Encode()), map[string]string{ - "Content-Type": "application/x-www-form-urlencoded", - "X-QMAuth": hash, - }) - } - - if err == nil { - if err = v.DoJSON(req, &tokens); err == nil && tokens.IDToken == "" { - err = errors.New("missing id token (1)") - } - } - } + identity := &vw.Identity{Client: v.Client} + idToken, err := identity.Login(query, v.user, v.password) if err == nil { data := url.Values(map[string][]string{ "grant_type": {"id_token"}, "scope": {"sc2:fal"}, - "token": {tokens.IDToken}, + "token": {idToken}, }) req, err = request.New(http.MethodPost, vw.OauthTokenURI, strings.NewReader(data.Encode()), map[string]string{ "Content-Type": "application/x-www-form-urlencoded", - "X-Client-Id": audiOAuthClientID, + "X-Client-Id": audiClientID, }) if err == nil { - if err = v.DoJSON(req, &v.tokens); err == nil && tokens.IDToken == "" { - err = errors.New("missing id token (2)") + if err = v.DoJSON(req, &v.tokens); err == nil && v.tokens.AccessToken == "" { + err = errors.New("missing access token") } } } @@ -185,6 +128,6 @@ func (v *Audi) refreshHeaders() map[string]string { "Content-Type": "application/x-www-form-urlencoded", "X-App-Version": "3.14.0", "X-App-Name": "myAudi", - "X-Client-Id": audiOAuthClientID, + "X-Client-Id": audiClientID, } } diff --git a/vehicle/vw.go b/vehicle/vw.go index e20089b42..7f1434bc0 100644 --- a/vehicle/vw.go +++ b/vehicle/vw.go @@ -82,60 +82,24 @@ func NewVWFromConfig(other map[string]interface{}) (api.Vehicle, error) { } func (v *VW) authFlow() error { - var err error var uri string var req *http.Request - var resp *http.Response - var idToken string - // execute login - challenge, verifier, err := vw.ChallengeVerifier() + const clientID = "9496332b-ea03-4091-a224-8c746b885068@apps_vw-dilab_com" + const redirectURI = "carnet://identity-kit/login" + query := url.Values(map[string][]string{ - "prompt": {"login"}, - "state": {vw.RandomString(43)}, - "response_type": {"code id_token token"}, - "code_challenge_method": {"s256"}, + "response_type": {"id_token token"}, + "client_id": {clientID}, + "redirect_uri": {redirectURI}, "scope": {"openid profile mbb cars birthdate nickname address phone"}, - "code_challenge": {challenge}, - "redirect_uri": {"carnet://identity-kit/login"}, - "client_id": {"9496332b-ea03-4091-a224-8c746b885068@apps_vw-dilab_com"}, + "state": {vw.RandomString(43)}, "nonce": {vw.RandomString(43)}, + "prompt": {"login"}, }) - uri = "https://identity.vwgroup.io/oidc/v1/authorize?" + query.Encode() - if err == nil { - identity := &vw.Identity{Client: v.Client} - resp, err = identity.Login(uri, v.user, v.password) - } - - if err == nil { - // var code string - - loc := strings.ReplaceAll(resp.Header.Get("Location"), "#", "?") // convert to parsable url - if locationURL, err := url.Parse(loc); err == nil { - // code = locationURL.Query().Get("code") - idToken = locationURL.Query().Get("id_token") - } - - _ = verifier - // if err == nil { - // data := url.Values(map[string][]string{ - // "auth_code": {code}, - // "code_verifier": {verifier}, - // "id_token": {idToken}, - // }) - - // uri := "https://tokenrefreshservice.apps.emea.vwapps.io/exchangeAuthCode" - // req, err = request.New(http.MethodPost, uri, strings.NewReader(data.Encode()), request.URLEncoding) - - // // if err == nil { - // // err = v.DoJSON(req, &tokens) - // // if err == nil && tokens.AccessToken == "" { - // // err = errors.New("missing access token") - // // } - // // } - // } - } + identity := &vw.Identity{Client: v.Client} + idToken, err := identity.Login(query, v.user, v.password) // get client id if err == nil { diff --git a/vehicle/vw/identity.go b/vehicle/vw/identity.go index 43e3f3777..6e57a6c36 100644 --- a/vehicle/vw/identity.go +++ b/vehicle/vw/identity.go @@ -1,6 +1,7 @@ package vw import ( + "errors" "net/http" "net/url" "strings" @@ -51,11 +52,12 @@ func (v *Identity) redirect(resp *http.Response, err error) (*http.Response, err } // Login performs the identity.vwgroup.io login -func (v *Identity) Login(uri, user, password string) (*http.Response, error) { +func (v *Identity) Login(query url.Values, user, password string) (string, error) { var vars FormVars var req *http.Request // GET identity.vwgroup.io/oidc/v1/authorize?ui_locales=de&scope=openid%20profile%20birthdate%20nickname%20address%20phone%20cars%20mbb&response_type=code&state=gmiJOaB4&redirect_uri=https%3A%2F%2Fwww.portal.volkswagen-we.com%2Fportal%2Fweb%2Fguest%2Fcomplete-login&nonce=38042ee3-b7a7-43cf-a9c1-63d2f3f2d9f3&prompt=login&client_id=b7a5bb47-f875-47cf-ab83-2ba3bf6bb738@apps_vw-dilab_com + uri := "https://identity.vwgroup.io/oidc/v1/authorize?" + query.Encode() resp, err := v.Get(uri) // GET identity.vwgroup.io/signin-service/v1/signin/b7a5bb47-f875-47cf-ab83-2ba3bf6bb738@apps_vw-dilab_com?relayState=15404cb51c8b4cc5efeee1d2c2a73e5b41562faa @@ -124,5 +126,19 @@ func (v *Identity) Login(uri, user, password string) (*http.Response, error) { resp, err = v.redirect(resp, err) } - return resp, err + var idToken string + if err == nil { + loc := strings.ReplaceAll(resp.Header.Get("Location"), "#", "?") // convert to parseable url + + var location *url.URL + if location, err = url.Parse(loc); err == nil { + idToken = location.Query().Get("id_token") + + if idToken == "" { + err = errors.New("missing id token") + } + } + } + + return idToken, err }