From 4fddf6595e53063c2e8db482535ba8a8e627afc9 Mon Sep 17 00:00:00 2001 From: Ivan Kara Date: Sat, 29 Jun 2024 18:42:24 +0700 Subject: [PATCH 1/5] add new HA user info api --- config/config.yml_example_homeassistant | 1 + pkg/providers/homeassistant/homeassistant.go | 23 +++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/config/config.yml_example_homeassistant b/config/config.yml_example_homeassistant index bbe860e2..ec9a603e 100644 --- a/config/config.yml_example_homeassistant +++ b/config/config.yml_example_homeassistant @@ -35,3 +35,4 @@ oauth: callback_url: https://vouch.yourdomain.com/auth auth_url: https://homeassistant.yourdomain.com:port/auth/authorize token_url: https://homeassistant.yourdomain.com:port/auth/token + user_info_url: https://homeassistant.yourdomain.com:port/api/user diff --git a/pkg/providers/homeassistant/homeassistant.go b/pkg/providers/homeassistant/homeassistant.go index c2b7ad0b..0235a647 100644 --- a/pkg/providers/homeassistant/homeassistant.go +++ b/pkg/providers/homeassistant/homeassistant.go @@ -11,7 +11,9 @@ OR CONDITIONS OF ANY KIND, either express or implied. package homeassistant import ( + "encoding/json" "golang.org/x/oauth2" + "io/ioutil" "net/http" "github.com/vouch/vouch-proxy/pkg/cfg" @@ -33,12 +35,27 @@ func (Provider) Configure() { // GetUserInfo provider specific call to get userinfomation // More info: https://developers.home-assistant.io/docs/en/auth_api.html func (Provider) GetUserInfo(r *http.Request, user *structs.User, customClaims *structs.CustomClaims, ptokens *structs.PTokens, opts ...oauth2.AuthCodeOption) (rerr error) { - _, providerToken, err := common.PrepareTokensAndClient(r, ptokens, false, opts...) + client, providerToken, err := common.PrepareTokensAndClient(r, ptokens, false, opts...) if err != nil { return err } ptokens.PAccessToken = providerToken.Extra("access_token").(string) - // Home assistant does not provide an API to query username, so we statically set it to "homeassistant" - user.Username = "homeassistant" + userinfo, err := client.Get(cfg.GenOAuth.UserInfoURL) + if err != nil { + return err + } + defer func() { + if err := userinfo.Body.Close(); err != nil { + rerr = err + } + }() + data, _ := ioutil.ReadAll(userinfo.Body) + log.Infof("HA userinfo body: %s", string(data)) + haUser := structs.User{} + if err = json.Unmarshal(data, &haUser); err != nil { + log.Error(err) + return err + } + user.Username = haUser.Username return nil } From afd511df7deea845306238a2befaa6ff5584b941 Mon Sep 17 00:00:00 2001 From: Ivan Kara Date: Sun, 30 Jun 2024 08:28:53 +0700 Subject: [PATCH 2/5] add home assistant username fetch via websocket --- go.mod | 1 + go.sum | 2 + pkg/providers/homeassistant/homeassistant.go | 70 +++++++++++++++----- pkg/structs/structs.go | 13 +++- 4 files changed, 69 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 9b3e5f69..7d04bd0e 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,7 @@ require ( github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/gorilla/securecookie v1.1.2 // indirect + github.com/gorilla/websocket v1.5.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/influxdata/tdigest v0.0.1 // indirect github.com/josharian/intern v1.0.0 // indirect diff --git a/go.sum b/go.sum index f1f1b26c..b54be907 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,8 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/influxdata/tdigest v0.0.1 h1:XpFptwYmnEKUqmkcDjrzffswZ3nvNeevbUSLPP/ZzIY= diff --git a/pkg/providers/homeassistant/homeassistant.go b/pkg/providers/homeassistant/homeassistant.go index 0235a647..82747bbb 100644 --- a/pkg/providers/homeassistant/homeassistant.go +++ b/pkg/providers/homeassistant/homeassistant.go @@ -12,10 +12,11 @@ package homeassistant import ( "encoding/json" - "golang.org/x/oauth2" - "io/ioutil" "net/http" + "golang.org/x/oauth2" + + "github.com/gorilla/websocket" "github.com/vouch/vouch-proxy/pkg/cfg" "github.com/vouch/vouch-proxy/pkg/providers/common" "github.com/vouch/vouch-proxy/pkg/structs" @@ -32,30 +33,67 @@ func (Provider) Configure() { log = cfg.Logging.Logger } +type AuthMessage struct { + Type string `json:"type"` + Token string `json:"auth_token"` +} + +type RequestMessage struct { + Id int `json:"id"` + Type string `json:"type"` +} + +type ResponseMessage struct { + Id int `json:"id"` + Result structs.HomeAssistantUser `json:"result"` +} + // GetUserInfo provider specific call to get userinfomation -// More info: https://developers.home-assistant.io/docs/en/auth_api.html +// More info: https://github.com/home-assistant/core/blob/5280291f98db41b6edd822a6b2fe6df4dea3df6a/homeassistant/components/auth/__init__.py#L484 +// Websocket API info: https://developers.home-assistant.io/docs/api/websocket func (Provider) GetUserInfo(r *http.Request, user *structs.User, customClaims *structs.CustomClaims, ptokens *structs.PTokens, opts ...oauth2.AuthCodeOption) (rerr error) { - client, providerToken, err := common.PrepareTokensAndClient(r, ptokens, false, opts...) + _, providerToken, err := common.PrepareTokensAndClient(r, ptokens, false, opts...) if err != nil { return err } ptokens.PAccessToken = providerToken.Extra("access_token").(string) - userinfo, err := client.Get(cfg.GenOAuth.UserInfoURL) + + wsURL := cfg.GenOAuth.UserInfoURL + client, _, err := websocket.DefaultDialer.Dial(wsURL, nil) + if err != nil { + return err + } + defer client.Close() + + authMessage := AuthMessage{ + Type: "auth", + Token: ptokens.PAccessToken, + } + if err := client.WriteJSON(authMessage); err != nil { + return err + } + _, _, err = client.ReadMessage() + if err != nil { + return err + } + + requestMessage := RequestMessage{ + Id: 10, // Can be any number but must be increased on each request + Type: "auth/current_user", + } + if err := client.WriteJSON(requestMessage); err != nil { + return err + } + _, responseMessage, err := client.ReadMessage() if err != nil { return err } - defer func() { - if err := userinfo.Body.Close(); err != nil { - rerr = err - } - }() - data, _ := ioutil.ReadAll(userinfo.Body) - log.Infof("HA userinfo body: %s", string(data)) - haUser := structs.User{} - if err = json.Unmarshal(data, &haUser); err != nil { - log.Error(err) + log.Infof("HA userinfo body: %s", string(responseMessage)) + var data ResponseMessage + if err := json.Unmarshal(responseMessage, &data); err != nil { return err } - user.Username = haUser.Username + data.Result.PrepareUserData() + user.Username = data.Result.Username return nil } diff --git a/pkg/structs/structs.go b/pkg/structs/structs.go index bccc0180..6eda6b68 100644 --- a/pkg/structs/structs.go +++ b/pkg/structs/structs.go @@ -130,6 +130,17 @@ func (u *GitHubUser) PrepareUserData() { u.Username = u.Login } +// HomeAssistantUser +type HomeAssistantUser struct { + User + IsAdmin bool `json:"is_admin"` + IsOwner bool `json:"is_owner"` +} + +func (u *HomeAssistantUser) PrepareUserData() { + u.Username = u.Name +} + // IndieAuthUser see indieauth.net type IndieAuthUser struct { User @@ -148,7 +159,7 @@ type Contact struct { Verified bool `json:"is_verified"` } -//OpenStaxUser is a retrieved and authenticated user from OpenStax Accounts +// OpenStaxUser is a retrieved and authenticated user from OpenStax Accounts type OpenStaxUser struct { User Contacts []Contact `json:"contact_infos"` From fd95633a982a5bf1ca1bfe8363dbb5803ad1ae9f Mon Sep 17 00:00:00 2001 From: Ivan Kara Date: Sun, 30 Jun 2024 09:44:35 +0700 Subject: [PATCH 3/5] fix requests add error logs --- pkg/providers/homeassistant/homeassistant.go | 39 +++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/pkg/providers/homeassistant/homeassistant.go b/pkg/providers/homeassistant/homeassistant.go index 82747bbb..ef61de70 100644 --- a/pkg/providers/homeassistant/homeassistant.go +++ b/pkg/providers/homeassistant/homeassistant.go @@ -35,7 +35,11 @@ func (Provider) Configure() { type AuthMessage struct { Type string `json:"type"` - Token string `json:"auth_token"` + Token string `json:"access_token"` +} + +type AuthResponse struct { + Type string `json:"type"` } type RequestMessage struct { @@ -44,8 +48,9 @@ type RequestMessage struct { } type ResponseMessage struct { - Id int `json:"id"` - Result structs.HomeAssistantUser `json:"result"` + Id int `json:"id"` + Success bool `json:"success"` + Result structs.HomeAssistantUser `json:"result"` } // GetUserInfo provider specific call to get userinfomation @@ -58,13 +63,19 @@ func (Provider) GetUserInfo(r *http.Request, user *structs.User, customClaims *s } ptokens.PAccessToken = providerToken.Extra("access_token").(string) - wsURL := cfg.GenOAuth.UserInfoURL - client, _, err := websocket.DefaultDialer.Dial(wsURL, nil) + client, _, err := websocket.DefaultDialer.Dial(cfg.GenOAuth.UserInfoURL, nil) if err != nil { + log.Errorf("error dialing HA websocket: %v", err) return err } defer client.Close() + _, _, err = client.ReadMessage() + if err != nil { + log.Errorf("error reading HA init message: %v", err) + return err + } + authMessage := AuthMessage{ Type: "auth", Token: ptokens.PAccessToken, @@ -72,10 +83,18 @@ func (Provider) GetUserInfo(r *http.Request, user *structs.User, customClaims *s if err := client.WriteJSON(authMessage); err != nil { return err } - _, _, err = client.ReadMessage() + _, authResponseData, err := client.ReadMessage() if err != nil { return err } + var authResponse AuthResponse + if err := json.Unmarshal(authResponseData, &authResponse); err != nil { + return err + } + if authResponse.Type != "auth_ok" { + log.Errorf("error authenticating with HA: %s", authResponseData) + return err + } requestMessage := RequestMessage{ Id: 10, // Can be any number but must be increased on each request @@ -89,10 +108,18 @@ func (Provider) GetUserInfo(r *http.Request, user *structs.User, customClaims *s return err } log.Infof("HA userinfo body: %s", string(responseMessage)) + if err = common.MapClaims(responseMessage, customClaims); err != nil { + log.Error(err) + return err + } var data ResponseMessage if err := json.Unmarshal(responseMessage, &data); err != nil { return err } + if !data.Success { + log.Errorf("error getting user info from HA: %s", responseMessage) + return err + } data.Result.PrepareUserData() user.Username = data.Result.Username return nil From 71795c15e83453a3b00643fbef058264dd3611d3 Mon Sep 17 00:00:00 2001 From: Ivan Kara Date: Sun, 30 Jun 2024 09:55:06 +0700 Subject: [PATCH 4/5] update HA example config --- config/config.yml_example_homeassistant | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.yml_example_homeassistant b/config/config.yml_example_homeassistant index ec9a603e..e4562dcd 100644 --- a/config/config.yml_example_homeassistant +++ b/config/config.yml_example_homeassistant @@ -35,4 +35,4 @@ oauth: callback_url: https://vouch.yourdomain.com/auth auth_url: https://homeassistant.yourdomain.com:port/auth/authorize token_url: https://homeassistant.yourdomain.com:port/auth/token - user_info_url: https://homeassistant.yourdomain.com:port/api/user + user_info_url: https://homeassistant.yourdomain.com:port/api/websocket From c6bec189d64cdbf63331a0d5572a8128ba5cb231 Mon Sep 17 00:00:00 2001 From: Ivan Kara Date: Sun, 30 Jun 2024 10:05:27 +0700 Subject: [PATCH 5/5] fix HA example config --- config/config.yml_example_homeassistant | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config/config.yml_example_homeassistant b/config/config.yml_example_homeassistant index e4562dcd..724810d5 100644 --- a/config/config.yml_example_homeassistant +++ b/config/config.yml_example_homeassistant @@ -35,4 +35,6 @@ oauth: callback_url: https://vouch.yourdomain.com/auth auth_url: https://homeassistant.yourdomain.com:port/auth/authorize token_url: https://homeassistant.yourdomain.com:port/auth/token - user_info_url: https://homeassistant.yourdomain.com:port/api/websocket + user_info_url: ws://homeassistant.yourdomain.com:port/api/websocket + # or if https: + #user_info_url: wss://homeassistant.yourdomain.com:port/api/websocket