From a42affd8271556a61f76cb517e7a83532d1a6495 Mon Sep 17 00:00:00 2001 From: andig Date: Thu, 7 Sep 2023 13:56:58 +0200 Subject: [PATCH] Seat: fix retrieving list of vehicles (#9703) --- vehicle/seat-cupra.go | 3 +- vehicle/seat.go | 61 +++++++++++++++++++++++++++++++++--- vehicle/seat/api.go | 53 +++++++++++++++++++++++++++++++ vehicle/seat/cupra/params.go | 16 ++++++++++ vehicle/seat/params.go | 13 +++----- 5 files changed, 130 insertions(+), 16 deletions(-) create mode 100644 vehicle/seat/api.go create mode 100644 vehicle/seat/cupra/params.go diff --git a/vehicle/seat-cupra.go b/vehicle/seat-cupra.go index b894e953f..167e71e61 100644 --- a/vehicle/seat-cupra.go +++ b/vehicle/seat-cupra.go @@ -8,7 +8,6 @@ import ( "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" - "github.com/evcc-io/evcc/vehicle/seat" "github.com/evcc-io/evcc/vehicle/seat/cupra" "github.com/evcc-io/evcc/vehicle/vag/service" "github.com/evcc-io/evcc/vehicle/vag/vwidentity" @@ -51,7 +50,7 @@ func NewCupraFromConfig(other map[string]interface{}) (api.Vehicle, error) { log := util.NewLogger("cupra").Redact(cc.User, cc.Password, cc.VIN) - ts, err := service.TokenRefreshServiceTokenSource(log, seat.TRSParams, seat.AuthParams, cc.User, cc.Password) + ts, err := service.TokenRefreshServiceTokenSource(log, cupra.TRSParams, cupra.AuthParams, cc.User, cc.Password) if err != nil { return nil, err } diff --git a/vehicle/seat.go b/vehicle/seat.go index 8709ff833..48b7ca2cb 100644 --- a/vehicle/seat.go +++ b/vehicle/seat.go @@ -1,14 +1,22 @@ package vehicle import ( + "context" + "fmt" + "net/http" + "net/url" + "strings" "time" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/request" "github.com/evcc-io/evcc/vehicle/seat" + "github.com/evcc-io/evcc/vehicle/seat/cupra" "github.com/evcc-io/evcc/vehicle/vag/service" + "github.com/evcc-io/evcc/vehicle/vag/vwidentity" "github.com/evcc-io/evcc/vehicle/vw" + "golang.org/x/oauth2" ) // https://github.com/trocotronic/weconnect @@ -50,18 +58,36 @@ func NewSeatFromConfig(other map[string]interface{}) (api.Vehicle, error) { log := util.NewLogger("seat").Redact(cc.User, cc.Password, cc.VIN) - trs, err := service.TokenRefreshServiceTokenSource(log, seat.TRSParams, seat.AuthParams, cc.User, cc.Password) + trs, err := service.TokenRefreshServiceTokenSource(log, cupra.TRSParams, cupra.AuthParams, cc.User, cc.Password) if err != nil { return nil, err } - ts := service.MbbTokenSource(log, trs, seat.AuthClientID) - api := vw.NewAPI(log, ts, seat.Brand, seat.Country) - api.Client.Timeout = cc.Timeout + // get OIDC user information + ctx := context.WithValue(context.Background(), oauth2.HTTPClient, request.NewClient(log)) + ui, err := vwidentity.Config.NewProvider(ctx).UserInfo(ctx, trs) + if err != nil { + return nil, fmt.Errorf("failed getting user information: %w", err) + } - cc.VIN, err = ensureVehicle(cc.VIN, api.Vehicles) + mbbId, err := mbbUserId(log, trs, ui.Subject) + if err != nil { + return nil, fmt.Errorf("failed getting mbbUserId: %w", err) + } + + ts := service.MbbTokenSource(log, trs, seat.AuthClientID) + api := seat.NewAPI(log, ts) + + cc.VIN, err = ensureVehicle( + cc.VIN, func() ([]string, error) { + return api.Vehicles(mbbId) + }, + ) if err == nil { + api := vw.NewAPI(log, ts, seat.Brand, seat.Country) + api.Client.Timeout = cc.Timeout + if err = api.HomeRegion(cc.VIN); err == nil { v.Provider = vw.NewProvider(api, cc.VIN, cc.Cache) } @@ -69,3 +95,28 @@ func NewSeatFromConfig(other map[string]interface{}) (api.Vehicle, error) { return v, err } + +func mbbUserId(log *util.Logger, ts oauth2.TokenSource, uid string) (string, error) { + client := request.NewHelper(log) + client.Transport = &oauth2.Transport{ + Source: ts, + Base: client.Transport, + } + + data := url.Values{ + "scopeId": []string{"commonMandatoryFields"}, + } + + var mandatoryConsentInfo struct { + MbbUserId string `json:"mbbUserId"` + } + + uri := fmt.Sprintf("https://profileintegrityservice.apps.emea.vwapps.io/iaa/pic/v1/users/%s/check-profile", uid) + req, err := request.New(http.MethodPost, uri, strings.NewReader(data.Encode()), request.URLEncoding) + + if err == nil { + err = client.DoJSON(req, &mandatoryConsentInfo) + } + + return mandatoryConsentInfo.MbbUserId, err +} diff --git a/vehicle/seat/api.go b/vehicle/seat/api.go new file mode 100644 index 000000000..36f8443ec --- /dev/null +++ b/vehicle/seat/api.go @@ -0,0 +1,53 @@ +package seat + +import ( + "fmt" + + "github.com/evcc-io/evcc/util" + "github.com/evcc-io/evcc/util/request" + "golang.org/x/oauth2" +) + +// BaseURL is the API base url +const BaseURL = "https://mal-3a.prd.eu.dp.vwg-connect.com/api" + +// API provides list of vehicles for Seat cars +type API struct { + *request.Helper +} + +// NewAPI creates a new vehicle +func NewAPI(log *util.Logger, ts oauth2.TokenSource) *API { + v := &API{ + Helper: request.NewHelper(log), + } + + v.Client.Transport = &oauth2.Transport{ + Source: ts, + Base: v.Client.Transport, + } + + return v +} + +// Vehicles implements the /vehicles response +func (v *API) Vehicles(userID string) ([]string, error) { + var res struct { + UserVehicles struct { + UserId string + Vehicle []struct { + Content string + } + } + } + + uri := fmt.Sprintf("%s/usermanagement/users/v2/users/%s/vehicles", BaseURL, userID) + err := v.GetJSON(uri, &res) + + var vehicles []string + for _, v := range res.UserVehicles.Vehicle { + vehicles = append(vehicles, v.Content) + } + + return vehicles, err +} diff --git a/vehicle/seat/cupra/params.go b/vehicle/seat/cupra/params.go new file mode 100644 index 000000000..ce38d946c --- /dev/null +++ b/vehicle/seat/cupra/params.go @@ -0,0 +1,16 @@ +package cupra + +import "net/url" + +// Authorization parameters +var AuthParams = url.Values{ + "response_type": {"code id_token"}, // token + "client_id": {"30e33736-c537-4c72-ab60-74a7b92cfe83@apps_vw-dilab_com"}, + "redirect_uri": {"cupraconnect://identity-kit/login"}, + "scope": {"openid profile mbb"}, // cars birthdate nickname address phone +} + +// TokenRefreshService parameters +var TRSParams = url.Values{ + "brand": {"cupra"}, +} diff --git a/vehicle/seat/params.go b/vehicle/seat/params.go index d16adfca8..ff58ab104 100644 --- a/vehicle/seat/params.go +++ b/vehicle/seat/params.go @@ -7,18 +7,13 @@ const ( Country = "ES" // Authorization ClientID - AuthClientID = "9d183b70-d129-424f-9a26-c3778edf95e1" + AuthClientID = "9dcc70f0-8e79-423a-a3fa-4065d99088b4" ) // Authorization parameters var AuthParams = url.Values{ "response_type": {"code id_token"}, // token - "client_id": {"30e33736-c537-4c72-ab60-74a7b92cfe83@apps_vw-dilab_com"}, - "redirect_uri": {"cupraconnect://identity-kit/login"}, - "scope": {"openid profile mbb"}, // cars birthdate nickname address phone -} - -// TokenRefreshService parameters -var TRSParams = url.Values{ - "brand": {"cupra"}, + "client_id": {"3c8e98bc-3ae9-4277-a563-d5ee65ddebba@apps_vw-dilab_com"}, + "redirect_uri": {"seatconnect://identity-kit/login"}, + "scope": {"openid profile"}, // address phone email birthdate nationalIdentifier cars mbb dealers badge nationality }