Add SponsorToken configuration for Easee (#920)

This commit is contained in:
andig 2021-04-25 18:59:00 +02:00 • committed by GitHub
parent ef06e9b489
commit 5681d1996b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 549 additions and 133 deletions

1
.gitignore vendored
View file

@ -20,3 +20,4 @@ soc-server
ca-cert.srl
server-key.pem
ca-key.pem
cmd/wip

View file

@ -29,14 +29,11 @@ func runCharger(cmd *cobra.Command, args []string) {
log.FATAL.Fatal(err)
}
// setup mqtt
if conf.Mqtt.Broker != "" {
configureMQTT(conf.Mqtt)
// setup environment
if err := configureEnvironment(conf); err != nil {
log.FATAL.Fatal(err)
}
// setup javascript VMs
configureJavascript(conf.Javascript)
if err := cp.configureChargers(conf); err != nil {
log.FATAL.Fatal(err)
}

View file

@ -14,22 +14,23 @@ import (
)
type config struct {
URI string
Log string
Metrics bool
Profile bool
Levels map[string]string
Interval time.Duration
Mqtt mqttConfig
Javascript map[string]interface{}
Influx server.InfluxConfig
HEMS typedConfig
Messaging messagingConfig
Meters []qualifiedConfig
Chargers []qualifiedConfig
Vehicles []qualifiedConfig
Site map[string]interface{}
LoadPoints []map[string]interface{}
URI string
Log string
SponsorToken string
Metrics bool
Profile bool
Levels map[string]string
Interval time.Duration
Mqtt mqttConfig
Javascript map[string]interface{}
Influx server.InfluxConfig
HEMS typedConfig
Messaging messagingConfig
Meters []qualifiedConfig
Chargers []qualifiedConfig
Vehicles []qualifiedConfig
Site map[string]interface{}
LoadPoints []map[string]interface{}
}
type mqttConfig struct {

View file

@ -31,15 +31,12 @@ func runDump(cmd *cobra.Command, args []string) {
log.FATAL.Fatal(err)
}
// setup mqtt
if conf.Mqtt.Broker != "" {
configureMQTT(conf.Mqtt)
// setup environment
if err := configureEnvironment(conf); err != nil {
log.FATAL.Fatal(err)
}
// setup javascript VMs
configureJavascript(conf.Javascript)
site, err := loadConfig(conf)
site, err := configureSiteAndLoadpoints(conf)
if err != nil {
log.FATAL.Fatal(err)
}

View file

@ -29,14 +29,11 @@ func runMeter(cmd *cobra.Command, args []string) {
log.FATAL.Fatal(err)
}
// setup mqtt
if conf.Mqtt.Broker != "" {
configureMQTT(conf.Mqtt)
// setup environment
if err := configureEnvironment(conf); err != nil {
log.FATAL.Fatal(err)
}
// setup javascript VMs
configureJavascript(conf.Javascript)
if err := cp.configureMeters(conf); err != nil {
log.FATAL.Fatal(err)
}

View file

@ -152,13 +152,16 @@ func run(cmd *cobra.Command, args []string) {
uri := viper.GetString("uri")
log.INFO.Println("listening at", uri)
// setup mqtt client listener
if conf.Mqtt.Broker != "" {
configureMQTT(conf.Mqtt)
// setup environment
if err := configureEnvironment(conf); err != nil {
log.FATAL.Fatal(err)
}
// setup javascript VMs
configureJavascript(conf.Javascript)
// setup loadpoints
site, err := configureSiteAndLoadpoints(conf)
if err != nil {
log.FATAL.Fatal(err)
}
// start broadcasting values
tee := &util.Tee{}
@ -167,12 +170,6 @@ func run(cmd *cobra.Command, args []string) {
cache := util.NewCache()
go cache.Run(pipe.NewDropper(ignoreErrors...).Pipe(tee.Attach()))
// setup loadpoints
site, err := loadConfig(conf)
if err != nil {
log.FATAL.Fatal(err)
}
// setup database
if conf.Influx.URL != "" {
configureDatabase(conf.Influx, site.LoadPoints(), tee.Attach())

View file

@ -1,6 +1,7 @@
package cmd
import (
"context"
"errors"
"fmt"
"math/rand"
@ -13,8 +14,11 @@ import (
"github.com/andig/evcc/provider/mqtt"
"github.com/andig/evcc/push"
"github.com/andig/evcc/server"
"github.com/andig/evcc/soc/proto/pb"
"github.com/andig/evcc/util"
"github.com/andig/evcc/util/cloud"
"github.com/andig/evcc/util/pipe"
"github.com/andig/evcc/util/sponsor"
"github.com/spf13/viper"
)
@ -24,6 +28,60 @@ func init() {
var cp = &ConfigProvider{}
func loadConfigFile(cfgFile string) (conf config, err error) {
if cfgFile != "" {
log.INFO.Println("using config file", cfgFile)
if err := viper.UnmarshalExact(&conf); err != nil {
log.FATAL.Fatalf("failed parsing config file %s: %v", cfgFile, err)
}
} else {
err = errors.New("missing evcc config")
}
return conf, err
}
func configureEnvironment(conf config) (err error) {
// setup sponsorship
if conf.SponsorToken != "" {
err = configureSponsorship(conf.SponsorToken)
}
// setup mqtt client listener
if err == nil && conf.Mqtt.Broker != "" {
err = configureMQTT(conf.Mqtt)
}
// setup javascript VMs
if err == nil {
err = configureJavascript(conf.Javascript)
}
return
}
func configureSponsorship(token string) error {
host := util.Getenv("GRPC_URI", cloud.Host)
conn, err := cloud.Connection(host)
if err != nil {
return err
}
client := pb.NewAuthClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
res, err := client.IsAuthorized(ctx, &pb.AuthRequest{Token: token})
if err == nil {
if res.Authorized {
sponsor.Subject = res.Subject
}
}
return err
}
// setup influx databases
func configureDatabase(conf server.InfluxConfig, loadPoints []core.LoadPointAPI, in <-chan util.Param) {
influx := server.NewInfluxClient(
@ -47,22 +105,25 @@ func configureDatabase(conf server.InfluxConfig, loadPoints []core.LoadPointAPI,
}
// setup mqtt
func configureMQTT(conf mqttConfig) {
func configureMQTT(conf mqttConfig) error {
log := util.NewLogger("mqtt")
clientID := mqtt.ClientID()
var err error
mqtt.Instance, err = mqtt.RegisteredClient(log, conf.Broker, conf.User, conf.Password, clientID, 1)
if err != nil {
log.FATAL.Fatalf("failed configuring mqtt: %v", err)
return fmt.Errorf("failed configuring mqtt: %w", err)
}
return nil
}
// setup javascript
func configureJavascript(conf map[string]interface{}) {
func configureJavascript(conf map[string]interface{}) error {
if err := javascript.Configure(conf); err != nil {
log.FATAL.Fatalf("failed configuring javascript: %v", err)
return fmt.Errorf("failed configuring javascript: %w", err)
}
return nil
}
// setup HEMS
@ -93,7 +154,7 @@ func configureMessengers(conf messagingConfig, cache *util.Cache) chan push.Even
return notificationChan
}
func loadConfig(conf config) (site *core.Site, err error) {
func configureSiteAndLoadpoints(conf config) (site *core.Site, err error) {
if err = cp.configure(conf); err == nil {
var loadPoints []*core.LoadPoint
loadPoints, err = configureLoadPoints(conf, cp)
@ -138,16 +199,3 @@ func configureLoadPoints(conf config, cp *ConfigProvider) (loadPoints []*core.Lo
return loadPoints, nil
}
func loadConfigFile(cfgFile string) (conf config, err error) {
if cfgFile != "" {
log.INFO.Println("using config file", cfgFile)
if err := viper.UnmarshalExact(&conf); err != nil {
log.FATAL.Fatalf("failed parsing config file %s: %v", cfgFile, err)
}
} else {
err = errors.New("missing evcc config")
}
return conf, err
}

View file

@ -29,14 +29,11 @@ func runVehicle(cmd *cobra.Command, args []string) {
log.FATAL.Fatal(err)
}
// setup mqtt
if conf.Mqtt.Broker != "" {
configureMQTT(conf.Mqtt)
// setup environment
if err := configureEnvironment(conf); err != nil {
log.FATAL.Fatal(err)
}
// setup javascript VMs
configureJavascript(conf.Javascript)
if err := cp.configureVehicles(conf); err != nil {
log.FATAL.Fatal(err)
}

View file

@ -1,6 +1,10 @@
uri: 0.0.0.0:7070 # uri for ui
interval: 10s # control cycle interval
# sponsor token enables optional features
# request sponsor token at https://cloud.evcc.io
sponsortoken:
# log settings
log: error
levels:

View file

@ -1,7 +1,7 @@
package charger
import (
"errors"
"errors"
"fmt"
"net/http"
"time"
@ -10,6 +10,7 @@ import (
"github.com/andig/evcc/internal/charger/easee"
"github.com/andig/evcc/util"
"github.com/andig/evcc/util/request"
"github.com/andig/evcc/util/sponsor"
)
// Easee charger implementation
@ -49,7 +50,7 @@ func NewEaseeFromConfig(other map[string]interface{}) (api.Charger, error) {
func NewEasee(user, password, charger string, cache time.Duration) (*Easee, error) {
log := util.NewLogger("easee")
if true {
if !sponsor.IsAuthorized() {
return nil, errors.New("easee requires evcc sponsorship, register at https://cloud.evcc.io")
}

View file

@ -0,0 +1,16 @@
LICENSE
Copyright (c) 2019-2021 andig
This modules is NOT covered by the MIT license. All rights reserved.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -3,14 +3,13 @@ package vehicle
import (
"context"
"errors"
"os"
"time"
"github.com/andig/evcc/api"
"github.com/andig/evcc/internal/vehicle/cloud"
"github.com/andig/evcc/provider"
"github.com/andig/evcc/soc/proto/pb"
"github.com/andig/evcc/util"
"github.com/andig/evcc/util/cloud"
"github.com/andig/evcc/util/request"
)
@ -50,19 +49,18 @@ func NewCloudFromConfig(other map[string]interface{}) (api.Vehicle, error) {
return nil, errors.New("missing required token")
}
if host := os.Getenv("GRPC_URI"); host != "" {
cloud.Host = host
host := util.Getenv("GRPC_URI", cloud.Host)
conn, err := cloud.Connection(host)
if err != nil {
return nil, err
}
log := util.NewLogger("cloud")
client, err := cloud.Client(log, cloud.Host)
v := &Cloud{
embed: &embed{cc.Title, cc.Capacity},
token: cc.Token,
brand: cc.Brand,
config: cc.Other,
client: client,
client: pb.NewVehicleClient(conn),
}
if err == nil {

View file

@ -21,6 +21,9 @@
"type": "string"
}
},
"sponsortoken": {
"type": "string"
},
"chargers": {
"type": "array",
"description": "List of chargers",

18
soc/proto/auth.proto Normal file
View file

@ -0,0 +1,18 @@
syntax = "proto3";
// protoc proto/auth.proto --go_out=. --go-grpc_out=.
option go_package = "proto/pb";
service Auth {
rpc IsAuthorized (AuthRequest) returns (AuthReply) {}
}
message AuthRequest {
string token = 1;
}
message AuthReply {
bool authorized = 1;
string subject = 2;
}

219
soc/proto/pb/auth.pb.go Normal file
View file

@ -0,0 +1,219 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.15.8
// source: proto/auth.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type AuthRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
}
func (x *AuthRequest) Reset() {
*x = AuthRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_auth_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AuthRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AuthRequest) ProtoMessage() {}
func (x *AuthRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_auth_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AuthRequest.ProtoReflect.Descriptor instead.
func (*AuthRequest) Descriptor() ([]byte, []int) {
return file_proto_auth_proto_rawDescGZIP(), []int{0}
}
func (x *AuthRequest) GetToken() string {
if x != nil {
return x.Token
}
return ""
}
type AuthReply struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Authorized bool `protobuf:"varint,1,opt,name=authorized,proto3" json:"authorized,omitempty"`
Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"`
}
func (x *AuthReply) Reset() {
*x = AuthReply{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_auth_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AuthReply) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AuthReply) ProtoMessage() {}
func (x *AuthReply) ProtoReflect() protoreflect.Message {
mi := &file_proto_auth_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AuthReply.ProtoReflect.Descriptor instead.
func (*AuthReply) Descriptor() ([]byte, []int) {
return file_proto_auth_proto_rawDescGZIP(), []int{1}
}
func (x *AuthReply) GetAuthorized() bool {
if x != nil {
return x.Authorized
}
return false
}
func (x *AuthReply) GetSubject() string {
if x != nil {
return x.Subject
}
return ""
}
var File_proto_auth_proto protoreflect.FileDescriptor
var file_proto_auth_proto_rawDesc = []byte{
0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x22, 0x23, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x45, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x52,
0x65, 0x70, 0x6c, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a,
0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72,
0x69, 0x7a, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x32, 0x32,
0x0a, 0x04, 0x41, 0x75, 0x74, 0x68, 0x12, 0x2a, 0x0a, 0x0c, 0x49, 0x73, 0x41, 0x75, 0x74, 0x68,
0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x0c, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x0a, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x70, 0x6c, 0x79,
0x22, 0x00, 0x42, 0x0a, 0x5a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x62, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_proto_auth_proto_rawDescOnce sync.Once
file_proto_auth_proto_rawDescData = file_proto_auth_proto_rawDesc
)
func file_proto_auth_proto_rawDescGZIP() []byte {
file_proto_auth_proto_rawDescOnce.Do(func() {
file_proto_auth_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_auth_proto_rawDescData)
})
return file_proto_auth_proto_rawDescData
}
var file_proto_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_proto_auth_proto_goTypes = []interface{}{
(*AuthRequest)(nil), // 0: AuthRequest
(*AuthReply)(nil), // 1: AuthReply
}
var file_proto_auth_proto_depIdxs = []int32{
0, // 0: Auth.IsAuthorized:input_type -> AuthRequest
1, // 1: Auth.IsAuthorized:output_type -> AuthReply
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_proto_auth_proto_init() }
func file_proto_auth_proto_init() {
if File_proto_auth_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_proto_auth_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AuthRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_auth_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AuthReply); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proto_auth_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_proto_auth_proto_goTypes,
DependencyIndexes: file_proto_auth_proto_depIdxs,
MessageInfos: file_proto_auth_proto_msgTypes,
}.Build()
File_proto_auth_proto = out.File
file_proto_auth_proto_rawDesc = nil
file_proto_auth_proto_goTypes = nil
file_proto_auth_proto_depIdxs = nil
}

View file

@ -0,0 +1,101 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package pb
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// AuthClient is the client API for Auth service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type AuthClient interface {
IsAuthorized(ctx context.Context, in *AuthRequest, opts ...grpc.CallOption) (*AuthReply, error)
}
type authClient struct {
cc grpc.ClientConnInterface
}
func NewAuthClient(cc grpc.ClientConnInterface) AuthClient {
return &authClient{cc}
}
func (c *authClient) IsAuthorized(ctx context.Context, in *AuthRequest, opts ...grpc.CallOption) (*AuthReply, error) {
out := new(AuthReply)
err := c.cc.Invoke(ctx, "/Auth/IsAuthorized", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// AuthServer is the server API for Auth service.
// All implementations must embed UnimplementedAuthServer
// for forward compatibility
type AuthServer interface {
IsAuthorized(context.Context, *AuthRequest) (*AuthReply, error)
mustEmbedUnimplementedAuthServer()
}
// UnimplementedAuthServer must be embedded to have forward compatible implementations.
type UnimplementedAuthServer struct {
}
func (UnimplementedAuthServer) IsAuthorized(context.Context, *AuthRequest) (*AuthReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method IsAuthorized not implemented")
}
func (UnimplementedAuthServer) mustEmbedUnimplementedAuthServer() {}
// UnsafeAuthServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to AuthServer will
// result in compilation errors.
type UnsafeAuthServer interface {
mustEmbedUnimplementedAuthServer()
}
func RegisterAuthServer(s grpc.ServiceRegistrar, srv AuthServer) {
s.RegisterService(&Auth_ServiceDesc, srv)
}
func _Auth_IsAuthorized_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AuthRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AuthServer).IsAuthorized(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/Auth/IsAuthorized",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AuthServer).IsAuthorized(ctx, req.(*AuthRequest))
}
return interceptor(ctx, in, info, handler)
}
// Auth_ServiceDesc is the grpc.ServiceDesc for Auth service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Auth_ServiceDesc = grpc.ServiceDesc{
ServiceName: "Auth",
HandlerType: (*AuthServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "IsAuthorized",
Handler: _Auth_IsAuthorized_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "proto/auth.proto",
}

View file

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.15.6
// protoc v3.15.8
// source: proto/vehicle.proto
package pb

View file

@ -5,25 +5,25 @@ syntax = "proto3";
option go_package = "proto/pb";
service Vehicle {
rpc New (NewRequest) returns (NewReply) {}
rpc SoC (SoCRequest) returns (SoCReply) {}
rpc New (NewRequest) returns (NewReply) {}
rpc SoC (SoCRequest) returns (SoCReply) {}
}
message NewRequest {
string token = 1;
string type = 2;
map<string,string> config = 3;
string token = 1;
string type = 2;
map<string,string> config = 3;
}
message NewReply {
int64 vehicle_id = 1;
int64 vehicle_id = 1;
}
message SoCRequest {
string token = 1;
int64 vehicle_id = 2;
string token = 1;
int64 vehicle_id = 2;
}
message SoCReply {
double soc = 1;
double soc = 1;
}

40
soc/server/server/auth.go Normal file
View file

@ -0,0 +1,40 @@
package server
import (
"context"
"github.com/andig/evcc/soc/proto/pb"
"github.com/andig/evcc/soc/server/auth"
)
type AuthServer struct {
pb.UnimplementedAuthServer
}
func (s *AuthServer) IsAuthorized(ctx context.Context, r *pb.AuthRequest) (*pb.AuthReply, error) {
authorized, _, claims, err := isAuthorized(r)
res := &pb.AuthReply{Authorized: authorized}
if err == nil {
res.Subject = claims.Subject
}
return res, err
}
type tokenizer interface {
GetToken() string
}
func isAuthorized(r tokenizer) (bool, string, *auth.Claims, error) {
token := r.GetToken()
claims, err := auth.ParseToken(token)
if err != nil {
return false, token, claims, err
}
authorized, err := auth.IsAuthorized(claims.Subject)
return authorized, token, claims, err
}

View file

@ -58,17 +58,16 @@ func Run() {
log.Fatalf("failed to listen: %v", err)
}
srv := &Server{
vehicles: make(map[string]map[int64]api.Vehicle),
}
serverOptions := []grpc.ServerOption{}
if tlsConfig != nil {
serverOptions = append(serverOptions, grpc.Creds(credentials.NewTLS(tlsConfig)))
}
grpcServer := grpc.NewServer(serverOptions...)
pb.RegisterVehicleServer(grpcServer, srv)
pb.RegisterVehicleServer(grpcServer, &VehicleServer{
vehicles: make(map[string]map[int64]api.Vehicle),
})
pb.RegisterAuthServer(grpcServer, &AuthServer{})
log.Fatal(grpcServer.Serve(listener))
}

View file

@ -7,44 +7,23 @@ import (
"github.com/andig/evcc/api"
"github.com/andig/evcc/internal/vehicle"
"github.com/andig/evcc/internal/vehicle/cloud"
"github.com/andig/evcc/soc/proto/pb"
"github.com/andig/evcc/soc/server/auth"
"github.com/andig/evcc/util/cloud"
)
var vehicleID int64
type Server struct {
type VehicleServer struct {
vehicles map[string]map[int64]api.Vehicle
pb.UnimplementedVehicleServer
}
type tokenizer interface {
GetToken() string
}
func (s *Server) isAuthorized(r tokenizer) (string, *auth.Claims, error) {
token := r.GetToken()
claims, err := auth.ParseToken(token)
if err != nil {
return token, claims, err
}
authorized, err := auth.IsAuthorized(claims.Subject)
if err == nil && !authorized {
err = cloud.ErrNotAuthorized
}
return token, claims, err
}
type vehicler interface {
tokenizer
GetVehicleId() int64
}
func (s *Server) vehicle(r vehicler) (api.Vehicle, error) {
func (s *VehicleServer) vehicle(r vehicler) (api.Vehicle, error) {
token := r.GetToken()
vehicles, ok := s.vehicles[token]
if !ok {
@ -70,11 +49,14 @@ func stringMapToInterface(in map[string]string) map[string]interface{} {
return res
}
func (s *Server) New(ctx context.Context, r *pb.NewRequest) (*pb.NewReply, error) {
token, claims, err := s.isAuthorized(r)
func (s *VehicleServer) New(ctx context.Context, r *pb.NewRequest) (*pb.NewReply, error) {
authorized, token, claims, err := isAuthorized(r)
if err != nil {
return nil, err
}
if !authorized {
return nil, cloud.ErrNotAuthorized
}
typ := r.GetType()
config := r.GetConfig()
@ -100,7 +82,7 @@ func (s *Server) New(ctx context.Context, r *pb.NewRequest) (*pb.NewReply, error
return &res, nil
}
func (s *Server) SoC(ctx context.Context, r *pb.SoCRequest) (*pb.SoCReply, error) {
func (s *VehicleServer) SoC(ctx context.Context, r *pb.SoCRequest) (*pb.SoCReply, error) {
v, err := s.vehicle(r)
if err != nil {
return nil, err

View file

@ -5,8 +5,6 @@ import (
"crypto/x509"
"fmt"
"github.com/andig/evcc/soc/proto/pb"
"github.com/andig/evcc/util"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
@ -14,8 +12,7 @@ import (
var Host = "cloud.evcc.io:8080"
var (
conn *grpc.ClientConn
client pb.VehicleClient
conn *grpc.ClientConn
)
func loadTLSCredentials() (*tls.Config, error) {
@ -32,7 +29,7 @@ func loadTLSCredentials() (*tls.Config, error) {
return config, nil
}
func Client(log *util.Logger, uri string) (pb.VehicleClient, error) {
func Connection(uri string) (*grpc.ClientConn, error) {
var err error
if conn == nil {
var tlsConfig *tls.Config
@ -48,9 +45,5 @@ func Client(log *util.Logger, uri string) (pb.VehicleClient, error) {
conn, err = grpc.Dial(uri, transportOption)
}
if client == nil && err == nil {
client = pb.NewVehicleClient(conn)
}
return client, err
return conn, err
}

7
util/sponsor/auth.go Normal file
View file

@ -0,0 +1,7 @@
package sponsor
var Subject string
func IsAuthorized() bool {
return len(Subject) > 0
}