Add VW api (pre-ID portal-based version) (#361)
This commit is contained in:
parent
26eeb05c05
commit
03dd2f4128
3 changed files with 434 additions and 121 deletions
127
vehicle/audi.go
127
vehicle/audi.go
|
|
@ -10,18 +10,17 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/provider"
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/andig/evcc/util/request"
|
||||
"github.com/andig/evcc/vehicle/vwidentity"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
const (
|
||||
vwIdentity = "https://identity.vwgroup.io"
|
||||
vwAPI = "https://msg.volkswagen.de/fs-car"
|
||||
vwToken = "https://mbboauth-1d.prd.ece.vwg-connect.com/mbbcoauth/mobile/oauth2/v1/token"
|
||||
vwAPI = "https://msg.volkswagen.de/fs-car"
|
||||
vwToken = "https://mbboauth-1d.prd.ece.vwg-connect.com/mbbcoauth/mobile/oauth2/v1/token"
|
||||
)
|
||||
|
||||
// OIDCResponse is the well-known OIDC provider response
|
||||
|
|
@ -133,15 +132,6 @@ func NewAudiFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
|||
return v, err
|
||||
}
|
||||
|
||||
func (v *Audi) redirect(resp *http.Response, err error) (*http.Response, error) {
|
||||
if err == nil {
|
||||
uri := resp.Header.Get("Location")
|
||||
resp, err = v.Get(uri)
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (v *Audi) request(method, uri string, data io.Reader, headers ...map[string]string) (*http.Request, error) {
|
||||
req, err := http.NewRequest(method, uri, data)
|
||||
if err != nil {
|
||||
|
|
@ -157,120 +147,17 @@ func (v *Audi) request(method, uri string, data io.Reader, headers ...map[string
|
|||
return req, nil
|
||||
}
|
||||
|
||||
type formVars struct {
|
||||
action string
|
||||
csrf string
|
||||
relayState string
|
||||
hmac string
|
||||
}
|
||||
|
||||
func formValues(reader io.Reader, id string) (formVars, error) {
|
||||
vars := formVars{}
|
||||
|
||||
doc, err := goquery.NewDocumentFromReader(reader)
|
||||
if err == nil {
|
||||
form := doc.Find(id).First()
|
||||
if form.Length() != 1 {
|
||||
return vars, errors.New("unexpected length")
|
||||
}
|
||||
|
||||
var exists bool
|
||||
vars.action, exists = form.Attr("action")
|
||||
if !exists {
|
||||
return vars, errors.New("attribute not found")
|
||||
}
|
||||
|
||||
vars.csrf, err = attr(form, "input[name=_csrf]", "value")
|
||||
if err == nil {
|
||||
vars.relayState, err = attr(form, "input[name=relayState]", "value")
|
||||
}
|
||||
if err == nil {
|
||||
vars.hmac, err = attr(form, "input[name=hmac]", "value")
|
||||
}
|
||||
}
|
||||
|
||||
return vars, err
|
||||
}
|
||||
|
||||
func attr(doc *goquery.Selection, path, attr string) (res string, err error) {
|
||||
sel := doc.Find(path)
|
||||
if sel.Length() != 1 {
|
||||
return "", errors.New("unexpected length")
|
||||
}
|
||||
|
||||
v, exists := sel.Attr(attr)
|
||||
if !exists {
|
||||
return "", errors.New("attribute not found")
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (v *Audi) authFlow() error {
|
||||
var err error
|
||||
var uri, body string
|
||||
var vars formVars
|
||||
var req *http.Request
|
||||
var resp *http.Response
|
||||
|
||||
uri = "https://identity.vwgroup.io/oidc/v1/authorize?" +
|
||||
"response_type=code&client_id=09b6cbec-cd19-4589-82fd-363dfa8c24da%40apps_vw-dilab_com&" +
|
||||
"redirect_uri=myaudi%3A%2F%2F%2F&scope=address%20profile%20badge%20birthdate%20birthplace%20nationalIdentifier%20nationality%20profession%20email%20vin%20phone%20nickname%20name%20picture%20mbb%20gallery%20openid&" +
|
||||
"state=7f8260b5-682f-4db8-b171-50a5189a1c08&nonce=583b9af2-7799-4c72-9cb0-e6c0f42b87b3&prompt=login&ui_locales=de-DE"
|
||||
resp, err = v.Get(uri)
|
||||
if err == nil {
|
||||
uri = resp.Header.Get("Location")
|
||||
resp, err = v.Get(uri)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
vars, err = formValues(resp.Body, "form#emailPasswordForm")
|
||||
}
|
||||
if err == nil {
|
||||
uri = vwIdentity + vars.action
|
||||
body := fmt.Sprintf(
|
||||
"_csrf=%s&relayState=%s&hmac=%s&email=%s",
|
||||
vars.csrf, vars.relayState, vars.hmac, url.QueryEscape(v.user),
|
||||
)
|
||||
req, err = http.NewRequest(http.MethodPost, uri, strings.NewReader(body))
|
||||
}
|
||||
if err == nil {
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp, err = v.Do(req)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
uri = vwIdentity + resp.Header.Get("Location")
|
||||
req, err = http.NewRequest(http.MethodGet, uri, nil)
|
||||
|
||||
}
|
||||
if err == nil {
|
||||
resp, err = v.Do(req)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
vars, err = formValues(resp.Body, "form#credentialsForm")
|
||||
}
|
||||
if err == nil {
|
||||
uri = vwIdentity + vars.action
|
||||
body = fmt.Sprintf(
|
||||
"_csrf=%s&relayState=%s&email=%s&hmac=%s&password=%s",
|
||||
vars.csrf,
|
||||
vars.relayState,
|
||||
url.QueryEscape(v.user),
|
||||
vars.hmac,
|
||||
url.QueryEscape(v.password),
|
||||
)
|
||||
req, err = http.NewRequest(http.MethodPost, uri, strings.NewReader(body))
|
||||
}
|
||||
if err == nil {
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp, err = v.Do(req)
|
||||
}
|
||||
|
||||
for i := 6; i < 9; i++ {
|
||||
resp, err = v.redirect(resp, err)
|
||||
}
|
||||
identity := &vwidentity.Identity{Client: v.Client}
|
||||
resp, err := identity.Login(uri, v.user, v.password)
|
||||
|
||||
var tokens audiTokenResponse
|
||||
if err == nil {
|
||||
|
|
@ -289,9 +176,7 @@ func (v *Audi) authFlow() error {
|
|||
url.QueryEscape("token id_token"),
|
||||
)
|
||||
|
||||
req, err = v.request(http.MethodPost, uri, strings.NewReader(body),
|
||||
map[string]string{"Content-Type": "application/x-www-form-urlencoded"},
|
||||
)
|
||||
req, err = request.New(http.MethodPost, uri, strings.NewReader(body), request.URLEncoding)
|
||||
}
|
||||
if err == nil {
|
||||
err = v.DoJSON(req, &tokens)
|
||||
|
|
|
|||
261
vehicle/vw.go
Normal file
261
vehicle/vw.go
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
package vehicle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/provider"
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/andig/evcc/util/request"
|
||||
"github.com/andig/evcc/vehicle/vwidentity"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
type vwVehiclesResponse struct {
|
||||
FullyLoadedVehiclesResponse struct {
|
||||
CompleteVehicles []struct {
|
||||
VIN string
|
||||
}
|
||||
VehiclesNotFullyLoaded []struct {
|
||||
VIN string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type vwChargerResponse struct {
|
||||
ErrorCode int `json:",string"`
|
||||
// /-/emanager/get-emanager
|
||||
EManager struct {
|
||||
RBC struct {
|
||||
Status struct {
|
||||
BatteryPercentage int
|
||||
ChargingRemaningHour int `json:",string"`
|
||||
ChargingRemaningMinute int `json:",string"`
|
||||
}
|
||||
}
|
||||
}
|
||||
// /-/vsr/get-vsr
|
||||
VehicleStatusData struct {
|
||||
BatteryRange int
|
||||
BatteryLevel int
|
||||
}
|
||||
}
|
||||
|
||||
// based on https://github.com/wez3/volkswagen-carnet-client
|
||||
|
||||
// VW is an api.Vehicle implementation for VW cars
|
||||
type VW struct {
|
||||
*embed
|
||||
*request.Helper
|
||||
user, password, vin string
|
||||
baseURI, csrf string
|
||||
chargeStateG func() (float64, error)
|
||||
finishTimeG func() (time.Time, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.Add("vw", NewVWFromConfig)
|
||||
}
|
||||
|
||||
// NewVWFromConfig creates a new vehicle
|
||||
func NewVWFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
||||
cc := struct {
|
||||
Title string
|
||||
Capacity int64
|
||||
User, Password, VIN string
|
||||
Cache time.Duration
|
||||
}{}
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log := util.NewLogger("vw")
|
||||
|
||||
v := &VW{
|
||||
embed: &embed{cc.Title, cc.Capacity},
|
||||
Helper: request.NewHelper(log),
|
||||
user: cc.User,
|
||||
password: cc.Password,
|
||||
vin: cc.VIN,
|
||||
}
|
||||
|
||||
v.chargeStateG = provider.NewCached(v.chargeState, cc.Cache).FloatGetter()
|
||||
v.finishTimeG = provider.NewCached(v.finishTime, cc.Cache).TimeGetter()
|
||||
|
||||
var err error
|
||||
jar, err := cookiejar.New(&cookiejar.Options{
|
||||
PublicSuffixList: publicsuffix.List,
|
||||
})
|
||||
|
||||
// track cookies and don't follow redirects
|
||||
v.Client.Jar = jar
|
||||
v.Client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = v.authFlow()
|
||||
}
|
||||
|
||||
if err == nil && cc.VIN == "" {
|
||||
v.vin, err = findVehicle(v.vehicles())
|
||||
if err == nil {
|
||||
log.DEBUG.Printf("found vehicle: %v", v.vin)
|
||||
}
|
||||
}
|
||||
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (v *VW) authFlow() error {
|
||||
var err error
|
||||
var uri, body string
|
||||
var vars vwidentity.FormVars
|
||||
var req *http.Request
|
||||
var resp *http.Response
|
||||
|
||||
// GET www.portal.volkswagen-we.com/portal/de_DE/web/guest/home
|
||||
uri = "https://www.portal.volkswagen-we.com/portal/de_DE/web/guest/home"
|
||||
resp, err = v.Get(uri)
|
||||
|
||||
if err == nil {
|
||||
vars, err = vwidentity.FormValues(resp.Body, "meta")
|
||||
}
|
||||
|
||||
// POST www.portal.volkswagen-we.com/portal/en_GB/web/guest/home/-/csrftokenhandling/get-login-url
|
||||
if err == nil {
|
||||
uri = "https://www.portal.volkswagen-we.com/portal/en_GB/web/guest/home/-/csrftokenhandling/get-login-url"
|
||||
if req, err = request.New(http.MethodPost, uri, nil, map[string]string{"X-CSRF-Token": vars.Csrf}); err == nil {
|
||||
|
||||
res := struct {
|
||||
ErrorCode int `json:",string"`
|
||||
LoginURL struct {
|
||||
Path string
|
||||
}
|
||||
}{}
|
||||
|
||||
if err = v.DoJSON(req, &res); err == nil {
|
||||
uri = strings.ReplaceAll(res.LoginURL.Path, " ", "%20")
|
||||
if res.ErrorCode != 0 {
|
||||
err = fmt.Errorf("login url error code: %d", res.ErrorCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// execute login
|
||||
if err == nil {
|
||||
identity := &vwidentity.Identity{Client: v.Client}
|
||||
resp, err = identity.Login(uri, v.user, v.password)
|
||||
}
|
||||
|
||||
// get base url
|
||||
if err == nil {
|
||||
var code, state string
|
||||
var locationURL *url.URL
|
||||
|
||||
location := resp.Header.Get("Location")
|
||||
|
||||
locationURL, err = url.Parse(location)
|
||||
if err == nil {
|
||||
code = locationURL.Query().Get("code")
|
||||
state = locationURL.Query().Get("state")
|
||||
}
|
||||
|
||||
uri = fmt.Sprintf(
|
||||
"%s?p_auth=%s&p_p_id=33_WAR_cored5portlet&p_p_lifecycle=1&p_p_state=normal&p_p_mode=view&p_p_col_id=column-1&p_p_col_count=1&_33_WAR_cored5portlet_javax.portlet.action=getLoginStatus",
|
||||
locationURL.Scheme+"://"+locationURL.Host+locationURL.Path,
|
||||
state,
|
||||
)
|
||||
|
||||
body = fmt.Sprintf("_33_WAR_cored5portlet_code=%s", url.QueryEscape(code))
|
||||
|
||||
req, err = request.New(http.MethodPost, uri, strings.NewReader(body), request.URLEncoding)
|
||||
if err == nil {
|
||||
resp, err = v.Do(req)
|
||||
uri = resp.Header.Get("Location")
|
||||
|
||||
v.baseURI = uri
|
||||
v.csrf = vars.Csrf
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (v *VW) vehicles() ([]string, error) {
|
||||
uri := v.baseURI + "/-/mainnavigation/get-fully-loaded-cars"
|
||||
req, err := request.New(http.MethodPost, uri, nil, map[string]string{
|
||||
"Accept": "application/json",
|
||||
"X-CSRF-Token": v.csrf,
|
||||
})
|
||||
|
||||
var res vwVehiclesResponse
|
||||
if err == nil {
|
||||
err = v.DoJSON(req, &res)
|
||||
}
|
||||
|
||||
vehicles := make([]string, 0)
|
||||
for _, v := range res.FullyLoadedVehiclesResponse.CompleteVehicles {
|
||||
vehicles = append(vehicles, v.VIN)
|
||||
}
|
||||
for _, v := range res.FullyLoadedVehiclesResponse.VehiclesNotFullyLoaded {
|
||||
vehicles = append(vehicles, v.VIN)
|
||||
}
|
||||
|
||||
return vehicles, err
|
||||
}
|
||||
|
||||
// chargeState implements the Vehicle.ChargeState interface
|
||||
func (v *VW) chargeState() (float64, error) {
|
||||
uri := v.baseURI + "/-/emanager/get-emanager"
|
||||
req, err := request.New(http.MethodPost, uri, nil, map[string]string{
|
||||
"Accept": "application/json",
|
||||
"X-CSRF-Token": v.csrf,
|
||||
})
|
||||
|
||||
var res vwChargerResponse
|
||||
if err == nil {
|
||||
err = v.DoJSON(req, &res)
|
||||
}
|
||||
|
||||
return float64(res.EManager.RBC.Status.BatteryPercentage), err
|
||||
}
|
||||
|
||||
// ChargeState implements the Vehicle.ChargeState interface
|
||||
func (v *VW) ChargeState() (float64, error) {
|
||||
return v.chargeStateG()
|
||||
}
|
||||
|
||||
// finishTime implements the Vehicle.ChargeFinishTimer interface
|
||||
func (v *VW) finishTime() (time.Time, error) {
|
||||
uri := v.baseURI + "/-/emanager/get-emanager"
|
||||
req, err := request.New(http.MethodPost, uri, nil, map[string]string{
|
||||
"Accept": "application/json",
|
||||
"X-CSRF-Token": v.csrf,
|
||||
})
|
||||
|
||||
var res vwChargerResponse
|
||||
if err == nil {
|
||||
err = v.DoJSON(req, &res)
|
||||
}
|
||||
|
||||
var duration time.Duration
|
||||
if err == nil {
|
||||
hour := res.EManager.RBC.Status.ChargingRemaningHour
|
||||
min := res.EManager.RBC.Status.ChargingRemaningMinute
|
||||
duration = time.Hour*time.Duration(hour) + time.Minute*time.Duration(min)
|
||||
}
|
||||
|
||||
return time.Now().Add(duration), err
|
||||
}
|
||||
|
||||
// FinishTime implements the Vehicle.ChargeFinishTimer interface
|
||||
func (v *VW) FinishTime() (time.Time, error) {
|
||||
return v.finishTimeG()
|
||||
}
|
||||
167
vehicle/vwidentity/vwidenity.go
Normal file
167
vehicle/vwidentity/vwidenity.go
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
package vwidentity
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/andig/evcc/util/request"
|
||||
)
|
||||
|
||||
const RootURI = "https://identity.vwgroup.io"
|
||||
|
||||
// Identity provides the identity.vwgroup.io login
|
||||
type Identity struct {
|
||||
*http.Client
|
||||
}
|
||||
|
||||
func (v *Identity) redirect(resp *http.Response, err error) (*http.Response, error) {
|
||||
if err == nil {
|
||||
uri := resp.Header.Get("Location")
|
||||
resp, err = v.Get(uri)
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
type FormVars struct {
|
||||
Action string
|
||||
Csrf string
|
||||
RelayState string
|
||||
Hmac string
|
||||
}
|
||||
|
||||
func FormValues(reader io.Reader, id string) (FormVars, error) {
|
||||
vars := FormVars{}
|
||||
|
||||
doc, err := goquery.NewDocumentFromReader(reader)
|
||||
if err == nil {
|
||||
// only interested in meta tag?
|
||||
if meta := doc.Find("meta[name=_csrf]"); id == "meta" {
|
||||
if meta.Length() != 1 {
|
||||
return vars, errors.New("unexpected length")
|
||||
}
|
||||
|
||||
var exists bool
|
||||
vars.Csrf, exists = meta.Attr("content")
|
||||
if !exists {
|
||||
return vars, errors.New("meta not found")
|
||||
}
|
||||
return vars, nil
|
||||
}
|
||||
|
||||
form := doc.Find(id).First()
|
||||
if form.Length() != 1 {
|
||||
return vars, errors.New("unexpected length")
|
||||
}
|
||||
|
||||
var exists bool
|
||||
vars.Action, exists = form.Attr("action")
|
||||
if !exists {
|
||||
return vars, errors.New("attribute not found")
|
||||
}
|
||||
|
||||
vars.Csrf, err = attr(form, "input[name=_csrf]", "value")
|
||||
if err == nil {
|
||||
vars.RelayState, err = attr(form, "input[name=relayState]", "value")
|
||||
}
|
||||
if err == nil {
|
||||
vars.Hmac, err = attr(form, "input[name=hmac]", "value")
|
||||
}
|
||||
}
|
||||
|
||||
return vars, err
|
||||
}
|
||||
|
||||
func attr(doc *goquery.Selection, path, attr string) (res string, err error) {
|
||||
sel := doc.Find(path)
|
||||
if sel.Length() != 1 {
|
||||
return "", errors.New("unexpected length")
|
||||
}
|
||||
|
||||
v, exists := sel.Attr(attr)
|
||||
if !exists {
|
||||
return "", errors.New("attribute not found")
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Login performs the identity.vwgroup.io login
|
||||
func (v *Identity) Login(uri, user, password string) (*http.Response, 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
|
||||
resp, err := v.Get(uri)
|
||||
|
||||
// GET identity.vwgroup.io/signin-service/v1/signin/b7a5bb47-f875-47cf-ab83-2ba3bf6bb738@apps_vw-dilab_com?relayState=15404cb51c8b4cc5efeee1d2c2a73e5b41562faa
|
||||
if err == nil {
|
||||
uri = resp.Header.Get("Location")
|
||||
resp, err = v.Get(uri)
|
||||
|
||||
if err == nil {
|
||||
vars, err = FormValues(resp.Body, "form#emailPasswordForm")
|
||||
}
|
||||
}
|
||||
|
||||
// POST identity.vwgroup.io/signin-service/v1/b7a5bb47-f875-47cf-ab83-2ba3bf6bb738@apps_vw-dilab_com/login/identifier
|
||||
if err == nil {
|
||||
uri = RootURI + vars.Action
|
||||
|
||||
body := fmt.Sprintf(
|
||||
"_csrf=%s&relayState=%s&hmac=%s&email=%s",
|
||||
vars.Csrf, vars.RelayState, vars.Hmac, url.QueryEscape(user),
|
||||
)
|
||||
|
||||
req, err = request.New(http.MethodPost, uri, strings.NewReader(body), request.URLEncoding)
|
||||
if err == nil {
|
||||
resp, err = v.Do(req)
|
||||
}
|
||||
}
|
||||
|
||||
// GET identity.vwgroup.io/signin-service/v1/b7a5bb47-f875-47cf-ab83-2ba3bf6bb738@apps_vw-dilab_com/login/authenticate?relayState=15404cb51c8b4cc5efeee1d2c2a73e5b41562faa&email=...
|
||||
if err == nil {
|
||||
uri = RootURI + resp.Header.Get("Location")
|
||||
req, err = http.NewRequest(http.MethodGet, uri, nil)
|
||||
|
||||
if err == nil {
|
||||
resp, err = v.Do(req)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
vars, err = FormValues(resp.Body, "form#credentialsForm")
|
||||
}
|
||||
}
|
||||
|
||||
// POST identity.vwgroup.io/signin-service/v1/b7a5bb47-f875-47cf-ab83-2ba3bf6bb738@apps_vw-dilab_com/login/authenticate
|
||||
if err == nil {
|
||||
uri = RootURI + vars.Action
|
||||
body := fmt.Sprintf(
|
||||
"_csrf=%s&relayState=%s&email=%s&hmac=%s&password=%s",
|
||||
vars.Csrf,
|
||||
vars.RelayState,
|
||||
url.QueryEscape(user),
|
||||
vars.Hmac,
|
||||
url.QueryEscape(password),
|
||||
)
|
||||
|
||||
req, err = request.New(http.MethodPost, uri, strings.NewReader(body), request.URLEncoding)
|
||||
if err == nil {
|
||||
resp, err = v.Do(req)
|
||||
}
|
||||
}
|
||||
|
||||
// GET identity.vwgroup.io/oidc/v1/oauth/sso?clientId=b7a5bb47-f875-47cf-ab83-2ba3bf6bb738@apps_vw-dilab_com&relayState=15404cb51c8b4cc5efeee1d2c2a73e5b41562faa&userId=bca09cc0-8eba-4110-af71-7242868e1bf1&HMAC=2b01ce6a351fad4dd97dc8110d0967b46c95889ab5010c660a616462e66a83ca
|
||||
// GET identity.vwgroup.io/signin-service/v1/consent/users/bca09cc0-8eba-4110-af71-7242868e1bf1/b7a5bb47-f875-47cf-ab83-2ba3bf6bb738@apps_vw-dilab_com?scopes=openid%20profile%20birthdate%20nickname%20address%20phone%20cars%20mbb&relayState=15404cb51c8b4cc5efeee1d2c2a73e5b41562faa&callback=https://identity.vwgroup.io/oidc/v1/oauth/client/callback&hmac=a590931ca3cd9dc3a27f1d1c0c162bf1e5c5c32c9f5b40fcb36d4c6edc631e03
|
||||
// GET identity.vwgroup.io/oidc/v1/oauth/client/callback/success?user_id=bca09cc0-8eba-4110-af71-7242868e1bf1&client_id=b7a5bb47-f875-47cf-ab83-2ba3bf6bb738@apps_vw-dilab_com&scopes=openid%20profile%20birthdate%20nickname%20address%20phone%20cars%20mbb&consentedScopes=openid%20profile%20birthdate%20nickname%20address%20phone%20cars%20mbb&relayState=f89a0b750c93e278a7ace170ce374e9cb9eb0a74&hmac=2b728f463c3cfe80f3271fbb35680e5e5218ca70025a46e7fadf7c7982decc2b
|
||||
for i := 6; i < 9; i++ {
|
||||
resp, err = v.redirect(resp, err)
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue