diff --git a/README.md b/README.md index c3d2e4ec..46220dc4 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,28 @@ persistence: database: 0 ``` +## Authentication +Gmqtt provides a simple username/password authentication mechanism. (Provided by [auth](https://github.com/DrmagicE/gmqtt/blob/master/plugin/auth) plugin). +It is not enabled in default configuration, you can change the configuration to enable it: +```yaml +# plugin loading orders +plugin_order: + - auth + - prometheus + - admin +``` +When auth plugin enabled, every clients need an account to get connected.You can add accounts through the HTTP API: +```bash +# Create: username = user1, password = user1pass +$ curl -X POST -d '{"password":"user1pass"}' 127.0.0.1:8083/v1/accounts/user1 +{} +# Query +$ curl 127.0.0.1:8083/v1/accounts/user1 +{"account":{"username":"user1","password":"20a0db53bc1881a7f739cd956b740039"}} +``` +API Doc [swagger](https://github.com/DrmagicE/gmqtt/blob/master/plugin/auth/swagger) + + ## Docker ``` $ docker build -t gmqtt . diff --git a/README_ZH.md b/README_ZH.md index eff0c67c..78ff5b6f 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -51,6 +51,28 @@ persistence: database: 0 ``` +## 配置鉴权 +Gmqtt内置了基于username/password的简单鉴权机制。(由 [auth](https://github.com/DrmagicE/gmqtt/blob/master/plugin/auth) 插件提供)。 +Gmqtt默认配置没有开启鉴权,可以通过修改配置文件来加载鉴权插件: +```yaml +# plugin loading orders +plugin_order: + - auth + - prometheus + - admin +``` +加载后,需要添加账户才可以连接,可以通过HTTP接口来添加账户: +```bash +# 创建: username = user1, password = user1pass +$ curl -X POST -d '{"password":"user1pass"}' 127.0.0.1:8083/v1/accounts/user1 +{} +# 查询: +$ curl 127.0.0.1:8083/v1/accounts/user1 +{"account":{"username":"user1","password":"20a0db53bc1881a7f739cd956b740039"}} +``` +API文档:[swagger](https://github.com/DrmagicE/gmqtt/blob/master/plugin/auth/swagger) + + ## Docker ``` $ docker build -t gmqtt . diff --git a/cmd/gmqttd/default_config.yml b/cmd/gmqttd/default_config.yml index b5f1f662..24cd0946 100644 --- a/cmd/gmqttd/default_config.yml +++ b/cmd/gmqttd/default_config.yml @@ -1,15 +1,15 @@ listeners: - # bind address - - address: ":1883" - # tls setting -# tls: -# cert_file: "path_to_cert_file" -# key_file: "path_to_key_file" + # bind address + - address: ":1883" + # tls setting + # tls: + # cert_file: "path_to_cert_file" + # key_file: "path_to_key_file" - - address: ":8883" - # websocket setting - websocket: - path: "/" + - address: ":8883" + # websocket setting + websocket: + path: "/" mqtt: session_expiry: 2h session_expiry_check_timer: 20s @@ -62,6 +62,19 @@ plugins: addr: :8083 grpc: addr: 8084 + auth: + # Password hash type. (plain | md5 | sha256 | bcrypt) + # Default to MD5. + hash: md5 + # The file to store password. Default to $HOME/gmqtt_password.yml + # password_file: + +# plugin loading orders +plugin_order: + # Uncomment auth to enable authentication. + #- auth + - prometheus + - admin log: level: info # debug | info | warn | error format: text # json | text diff --git a/cmd/gmqttd/plugins.go b/cmd/gmqttd/plugins.go index 3714e750..6ba33d5d 100644 --- a/cmd/gmqttd/plugins.go +++ b/cmd/gmqttd/plugins.go @@ -1,16 +1,7 @@ package main import ( - "github.com/DrmagicE/gmqtt/plugin/admin" - "github.com/DrmagicE/gmqtt/plugin/prometheus" - "github.com/DrmagicE/gmqtt/server" + _ "github.com/DrmagicE/gmqtt/plugin/admin" + _ "github.com/DrmagicE/gmqtt/plugin/auth" + _ "github.com/DrmagicE/gmqtt/plugin/prometheus" ) - -var pluginOrder = []string{ - prometheus.Name, - admin.Name, -} - -func init() { - server.SetPluginOrder(pluginOrder) -} diff --git a/config/config.go b/config/config.go index b24afbc7..cb2abe3d 100644 --- a/config/config.go +++ b/config/config.go @@ -14,7 +14,7 @@ var ( defaultPluginConfig = make(map[string]Configuration) ) -// Configuration is the interface that enable the implementation can parse config from the global config file. +// Configuration is the interface that enable the implementation to parse config from the global config file. // Plugin admin and prometheus are two examples. type Configuration interface { // Validate validates the configuration. @@ -103,11 +103,15 @@ func (p pluginConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { // Config is the configration for gmqttd. type Config struct { - Listeners []*ListenerConfig `yaml:"listeners"` - MQTT MQTT `yaml:"mqtt,omitempty"` - Log LogConfig `yaml:"log"` - PidFile string `yaml:"pid_file"` - Plugins pluginConfig `yaml:"plugins"` + Listeners []*ListenerConfig `yaml:"listeners"` + MQTT MQTT `yaml:"mqtt,omitempty"` + Log LogConfig `yaml:"log"` + PidFile string `yaml:"pid_file"` + Plugins pluginConfig `yaml:"plugins"` + // PluginOrder is a slice that contains the name of the plugin which will be loaded. + // Giving a correct order to the slice is significant, + // because it represents the loading order which affect the behavior of the broker. + PluginOrder []string `yaml:"plugin_order"` Persistence Persistence `yaml:"persistence"` TopicAliasManager TopicAliasManager `yaml:"topic_alias_manager"` } diff --git a/go.mod b/go.mod index 32c6d014..36e5cce1 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/spf13/cobra v1.0.0 github.com/stretchr/testify v1.6.1 go.uber.org/zap v1.13.0 + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd golang.org/x/text v0.3.2 // indirect golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc // indirect diff --git a/go.sum b/go.sum index cdc8b9dd..c4b78fd1 100644 --- a/go.sum +++ b/go.sum @@ -208,6 +208,7 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= diff --git a/plugin/admin/admin.go b/plugin/admin/admin.go index 06850343..12bc0973 100644 --- a/plugin/admin/admin.go +++ b/plugin/admin/admin.go @@ -9,7 +9,9 @@ import ( grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" "github.com/grpc-ecosystem/grpc-gateway/runtime" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "github.com/DrmagicE/gmqtt/config" "github.com/DrmagicE/gmqtt/server" @@ -110,7 +112,12 @@ func (a *Admin) Load(service server.Server) error { log = server.LoggerWithField(zap.String("plugin", Name)) s := grpc.NewServer( grpc.ChainUnaryInterceptor( - grpc_zap.UnaryServerInterceptor(log), + grpc_zap.UnaryServerInterceptor(log, grpc_zap.WithLevels(func(code codes.Code) zapcore.Level { + if code == codes.OK { + return zapcore.DebugLevel + } + return grpc_zap.DefaultClientCodeToLevel(code) + })), grpc_prometheus.UnaryServerInterceptor), ) a.grpcServer = s diff --git a/plugin/admin/config.go b/plugin/admin/config.go index b3c2c3b9..0a80cc4e 100644 --- a/plugin/admin/config.go +++ b/plugin/admin/config.go @@ -44,10 +44,10 @@ func (c *Config) Validate() error { var DefaultConfig = Config{ HTTP: HTTPConfig{ Enable: true, - Addr: ":8083", + Addr: "127.0.0.1:8083", }, GRPC: GRPCConfig{ - Addr: ":8084", + Addr: "127.0.0.1:8084", }, } diff --git a/plugin/admin/utils.go b/plugin/admin/utils.go index 2bdbe573..d9313110 100644 --- a/plugin/admin/utils.go +++ b/plugin/admin/utils.go @@ -48,11 +48,17 @@ func (i *Indexer) Remove(id string) *list.Element { // GetByID returns the value for the given id. // Return nil if not found. +// Notice: Any access to the return *list.Element also require the mutex, +// because the Set method can modify the Value for *list.Element when updating the Value for the same id. +// If the caller needs the Value in *list.Element, it must get the Value before the next Set is called. func (i *Indexer) GetByID(id string) *list.Element { return i.index[id] } // Iterate iterates at most n elements in the list begin from offset. +// Notice: Any access to the *list.Element in fn also require the mutex, +// because the Set method can modify the Value for *list.Element when updating the Value for the same id. +// If the caller needs the Value in *list.Element, it must get the Value before the next Set is called. func (i *Indexer) Iterate(fn func(elem *list.Element), offset, n uint) { if i.rows.Len() < int(offset) { return diff --git a/plugin/auth/README.md b/plugin/auth/README.md new file mode 100644 index 00000000..d4e1ed85 --- /dev/null +++ b/plugin/auth/README.md @@ -0,0 +1,7 @@ +# Auth + +Auth plugin provides a simple username/password authentication mechanism. + +# API Doc + +See [swagger](https://github.com/DrmagicE/gmqtt/blob/master/plugin/auth/swagger) diff --git a/plugin/auth/account.pb.go b/plugin/auth/account.pb.go new file mode 100644 index 00000000..273c769b --- /dev/null +++ b/plugin/auth/account.pb.go @@ -0,0 +1,613 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.22.0 +// protoc v3.13.0 +// source: account.proto + +package auth + +import ( + proto "github.com/golang/protobuf/proto" + empty "github.com/golang/protobuf/ptypes/empty" + _ "google.golang.org/genproto/googleapis/api/annotations" + 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) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type ListAccountsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PageSize uint32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + Page uint32 `protobuf:"varint,2,opt,name=page,proto3" json:"page,omitempty"` +} + +func (x *ListAccountsRequest) Reset() { + *x = ListAccountsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_account_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListAccountsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAccountsRequest) ProtoMessage() {} + +func (x *ListAccountsRequest) ProtoReflect() protoreflect.Message { + mi := &file_account_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 ListAccountsRequest.ProtoReflect.Descriptor instead. +func (*ListAccountsRequest) Descriptor() ([]byte, []int) { + return file_account_proto_rawDescGZIP(), []int{0} +} + +func (x *ListAccountsRequest) GetPageSize() uint32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListAccountsRequest) GetPage() uint32 { + if x != nil { + return x.Page + } + return 0 +} + +type ListAccountsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Accounts []*Account `protobuf:"bytes,1,rep,name=accounts,proto3" json:"accounts,omitempty"` + TotalCount uint32 `protobuf:"varint,2,opt,name=total_count,json=totalCount,proto3" json:"total_count,omitempty"` +} + +func (x *ListAccountsResponse) Reset() { + *x = ListAccountsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_account_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListAccountsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAccountsResponse) ProtoMessage() {} + +func (x *ListAccountsResponse) ProtoReflect() protoreflect.Message { + mi := &file_account_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 ListAccountsResponse.ProtoReflect.Descriptor instead. +func (*ListAccountsResponse) Descriptor() ([]byte, []int) { + return file_account_proto_rawDescGZIP(), []int{1} +} + +func (x *ListAccountsResponse) GetAccounts() []*Account { + if x != nil { + return x.Accounts + } + return nil +} + +func (x *ListAccountsResponse) GetTotalCount() uint32 { + if x != nil { + return x.TotalCount + } + return 0 +} + +type GetAccountRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` +} + +func (x *GetAccountRequest) Reset() { + *x = GetAccountRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_account_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetAccountRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAccountRequest) ProtoMessage() {} + +func (x *GetAccountRequest) ProtoReflect() protoreflect.Message { + mi := &file_account_proto_msgTypes[2] + 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 GetAccountRequest.ProtoReflect.Descriptor instead. +func (*GetAccountRequest) Descriptor() ([]byte, []int) { + return file_account_proto_rawDescGZIP(), []int{2} +} + +func (x *GetAccountRequest) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +type GetAccountResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Account *Account `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"` +} + +func (x *GetAccountResponse) Reset() { + *x = GetAccountResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_account_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetAccountResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAccountResponse) ProtoMessage() {} + +func (x *GetAccountResponse) ProtoReflect() protoreflect.Message { + mi := &file_account_proto_msgTypes[3] + 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 GetAccountResponse.ProtoReflect.Descriptor instead. +func (*GetAccountResponse) Descriptor() ([]byte, []int) { + return file_account_proto_rawDescGZIP(), []int{3} +} + +func (x *GetAccountResponse) GetAccount() *Account { + if x != nil { + return x.Account + } + return nil +} + +type UpdateAccountRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` + Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` +} + +func (x *UpdateAccountRequest) Reset() { + *x = UpdateAccountRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_account_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateAccountRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateAccountRequest) ProtoMessage() {} + +func (x *UpdateAccountRequest) ProtoReflect() protoreflect.Message { + mi := &file_account_proto_msgTypes[4] + 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 UpdateAccountRequest.ProtoReflect.Descriptor instead. +func (*UpdateAccountRequest) Descriptor() ([]byte, []int) { + return file_account_proto_rawDescGZIP(), []int{4} +} + +func (x *UpdateAccountRequest) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *UpdateAccountRequest) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +type Account struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` + Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` +} + +func (x *Account) Reset() { + *x = Account{} + if protoimpl.UnsafeEnabled { + mi := &file_account_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Account) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Account) ProtoMessage() {} + +func (x *Account) ProtoReflect() protoreflect.Message { + mi := &file_account_proto_msgTypes[5] + 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 Account.ProtoReflect.Descriptor instead. +func (*Account) Descriptor() ([]byte, []int) { + return file_account_proto_rawDescGZIP(), []int{5} +} + +func (x *Account) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *Account) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +type DeleteAccountRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` +} + +func (x *DeleteAccountRequest) Reset() { + *x = DeleteAccountRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_account_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteAccountRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteAccountRequest) ProtoMessage() {} + +func (x *DeleteAccountRequest) ProtoReflect() protoreflect.Message { + mi := &file_account_proto_msgTypes[6] + 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 DeleteAccountRequest.ProtoReflect.Descriptor instead. +func (*DeleteAccountRequest) Descriptor() ([]byte, []int) { + return file_account_proto_rawDescGZIP(), []int{6} +} + +func (x *DeleteAccountRequest) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +var File_account_proto protoreflect.FileDescriptor + +var file_account_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x0e, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x1a, + 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, + 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x46, 0x0a, 0x13, 0x4c, 0x69, + 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x61, + 0x67, 0x65, 0x22, 0x6c, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x08, 0x61, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, + 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, + 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x22, 0x2f, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, + 0x65, 0x22, 0x47, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74, + 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x4e, 0x0a, 0x14, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, + 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x41, 0x0a, 0x07, 0x41, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x32, 0x0a, + 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, + 0x65, 0x32, 0xbd, 0x03, 0x0a, 0x0e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x67, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x23, 0x2e, 0x67, + 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x24, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x14, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0e, 0x12, + 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x6d, 0x0a, + 0x03, 0x47, 0x65, 0x74, 0x12, 0x21, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x75, 0x74, + 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, + 0x61, 0x75, 0x74, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x73, 0x2f, 0x7b, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x6a, 0x0a, 0x06, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x24, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, + 0x75, 0x74, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x22, 0x17, 0x2f, 0x76, + 0x31, 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x75, 0x73, 0x65, 0x72, + 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x3a, 0x01, 0x2a, 0x12, 0x67, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x12, 0x24, 0x2e, 0x67, 0x6d, 0x71, 0x74, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x2a, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, + 0x7d, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x3b, 0x61, 0x75, 0x74, 0x68, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var ( + file_account_proto_rawDescOnce sync.Once + file_account_proto_rawDescData = file_account_proto_rawDesc +) + +func file_account_proto_rawDescGZIP() []byte { + file_account_proto_rawDescOnce.Do(func() { + file_account_proto_rawDescData = protoimpl.X.CompressGZIP(file_account_proto_rawDescData) + }) + return file_account_proto_rawDescData +} + +var file_account_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_account_proto_goTypes = []interface{}{ + (*ListAccountsRequest)(nil), // 0: gmqtt.auth.api.ListAccountsRequest + (*ListAccountsResponse)(nil), // 1: gmqtt.auth.api.ListAccountsResponse + (*GetAccountRequest)(nil), // 2: gmqtt.auth.api.GetAccountRequest + (*GetAccountResponse)(nil), // 3: gmqtt.auth.api.GetAccountResponse + (*UpdateAccountRequest)(nil), // 4: gmqtt.auth.api.UpdateAccountRequest + (*Account)(nil), // 5: gmqtt.auth.api.Account + (*DeleteAccountRequest)(nil), // 6: gmqtt.auth.api.DeleteAccountRequest + (*empty.Empty)(nil), // 7: google.protobuf.Empty +} +var file_account_proto_depIdxs = []int32{ + 5, // 0: gmqtt.auth.api.ListAccountsResponse.accounts:type_name -> gmqtt.auth.api.Account + 5, // 1: gmqtt.auth.api.GetAccountResponse.account:type_name -> gmqtt.auth.api.Account + 0, // 2: gmqtt.auth.api.AccountService.List:input_type -> gmqtt.auth.api.ListAccountsRequest + 2, // 3: gmqtt.auth.api.AccountService.Get:input_type -> gmqtt.auth.api.GetAccountRequest + 4, // 4: gmqtt.auth.api.AccountService.Update:input_type -> gmqtt.auth.api.UpdateAccountRequest + 6, // 5: gmqtt.auth.api.AccountService.Delete:input_type -> gmqtt.auth.api.DeleteAccountRequest + 1, // 6: gmqtt.auth.api.AccountService.List:output_type -> gmqtt.auth.api.ListAccountsResponse + 3, // 7: gmqtt.auth.api.AccountService.Get:output_type -> gmqtt.auth.api.GetAccountResponse + 7, // 8: gmqtt.auth.api.AccountService.Update:output_type -> google.protobuf.Empty + 7, // 9: gmqtt.auth.api.AccountService.Delete:output_type -> google.protobuf.Empty + 6, // [6:10] is the sub-list for method output_type + 2, // [2:6] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_account_proto_init() } +func file_account_proto_init() { + if File_account_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_account_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListAccountsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_account_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListAccountsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_account_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetAccountRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_account_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetAccountResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_account_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateAccountRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_account_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Account); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_account_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteAccountRequest); 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_account_proto_rawDesc, + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_account_proto_goTypes, + DependencyIndexes: file_account_proto_depIdxs, + MessageInfos: file_account_proto_msgTypes, + }.Build() + File_account_proto = out.File + file_account_proto_rawDesc = nil + file_account_proto_goTypes = nil + file_account_proto_depIdxs = nil +} diff --git a/plugin/auth/account.pb.gw.go b/plugin/auth/account.pb.gw.go new file mode 100644 index 00000000..ac69e991 --- /dev/null +++ b/plugin/auth/account.pb.gw.go @@ -0,0 +1,472 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: account.proto + +/* +Package auth is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package auth + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage + +var ( + filter_AccountService_List_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_AccountService_List_0(ctx context.Context, marshaler runtime.Marshaler, client AccountServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListAccountsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AccountService_List_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.List(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AccountService_List_0(ctx context.Context, marshaler runtime.Marshaler, server AccountServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListAccountsRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_AccountService_List_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.List(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AccountService_Get_0(ctx context.Context, marshaler runtime.Marshaler, client AccountServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetAccountRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["username"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "username") + } + + protoReq.Username, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "username", err) + } + + msg, err := client.Get(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AccountService_Get_0(ctx context.Context, marshaler runtime.Marshaler, server AccountServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetAccountRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["username"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "username") + } + + protoReq.Username, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "username", err) + } + + msg, err := server.Get(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AccountService_Update_0(ctx context.Context, marshaler runtime.Marshaler, client AccountServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq UpdateAccountRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["username"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "username") + } + + protoReq.Username, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "username", err) + } + + msg, err := client.Update(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AccountService_Update_0(ctx context.Context, marshaler runtime.Marshaler, server AccountServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq UpdateAccountRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["username"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "username") + } + + protoReq.Username, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "username", err) + } + + msg, err := server.Update(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AccountService_Delete_0(ctx context.Context, marshaler runtime.Marshaler, client AccountServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteAccountRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["username"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "username") + } + + protoReq.Username, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "username", err) + } + + msg, err := client.Delete(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AccountService_Delete_0(ctx context.Context, marshaler runtime.Marshaler, server AccountServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteAccountRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["username"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "username") + } + + protoReq.Username, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "username", err) + } + + msg, err := server.Delete(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterAccountServiceHandlerServer registers the http handlers for service AccountService to "mux". +// UnaryRPC :call AccountServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +func RegisterAccountServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server AccountServiceServer) error { + + mux.Handle("GET", pattern_AccountService_List_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AccountService_List_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_AccountService_List_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_AccountService_Get_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AccountService_Get_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_AccountService_Get_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AccountService_Update_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AccountService_Update_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_AccountService_Update_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_AccountService_Delete_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AccountService_Delete_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_AccountService_Delete_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterAccountServiceHandlerFromEndpoint is same as RegisterAccountServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterAccountServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterAccountServiceHandler(ctx, mux, conn) +} + +// RegisterAccountServiceHandler registers the http handlers for service AccountService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterAccountServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterAccountServiceHandlerClient(ctx, mux, NewAccountServiceClient(conn)) +} + +// RegisterAccountServiceHandlerClient registers the http handlers for service AccountService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "AccountServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "AccountServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "AccountServiceClient" to call the correct interceptors. +func RegisterAccountServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client AccountServiceClient) error { + + mux.Handle("GET", pattern_AccountService_List_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AccountService_List_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_AccountService_List_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_AccountService_Get_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AccountService_Get_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_AccountService_Get_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AccountService_Update_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AccountService_Update_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_AccountService_Update_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_AccountService_Delete_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AccountService_Delete_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_AccountService_Delete_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_AccountService_List_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "accounts"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_AccountService_Get_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "accounts", "username"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_AccountService_Update_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "accounts", "username"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_AccountService_Delete_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "accounts", "username"}, "", runtime.AssumeColonVerbOpt(true))) +) + +var ( + forward_AccountService_List_0 = runtime.ForwardResponseMessage + + forward_AccountService_Get_0 = runtime.ForwardResponseMessage + + forward_AccountService_Update_0 = runtime.ForwardResponseMessage + + forward_AccountService_Delete_0 = runtime.ForwardResponseMessage +) diff --git a/plugin/auth/account_grpc.pb.go b/plugin/auth/account_grpc.pb.go new file mode 100644 index 00000000..ba837f52 --- /dev/null +++ b/plugin/auth/account_grpc.pb.go @@ -0,0 +1,218 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package auth + +import ( + context "context" + empty "github.com/golang/protobuf/ptypes/empty" + 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. +const _ = grpc.SupportPackageIsVersion7 + +// AccountServiceClient is the client API for AccountService 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 AccountServiceClient interface { + // List all accounts + List(ctx context.Context, in *ListAccountsRequest, opts ...grpc.CallOption) (*ListAccountsResponse, error) + // Get the account for given username. + // Return NotFound error when account not found. + Get(ctx context.Context, in *GetAccountRequest, opts ...grpc.CallOption) (*GetAccountResponse, error) + // Update the password for the account. + // This API will create the account if not exists. + Update(ctx context.Context, in *UpdateAccountRequest, opts ...grpc.CallOption) (*empty.Empty, error) + // Delete the account for given username + Delete(ctx context.Context, in *DeleteAccountRequest, opts ...grpc.CallOption) (*empty.Empty, error) +} + +type accountServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewAccountServiceClient(cc grpc.ClientConnInterface) AccountServiceClient { + return &accountServiceClient{cc} +} + +func (c *accountServiceClient) List(ctx context.Context, in *ListAccountsRequest, opts ...grpc.CallOption) (*ListAccountsResponse, error) { + out := new(ListAccountsResponse) + err := c.cc.Invoke(ctx, "/gmqtt.auth.api.AccountService/List", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *accountServiceClient) Get(ctx context.Context, in *GetAccountRequest, opts ...grpc.CallOption) (*GetAccountResponse, error) { + out := new(GetAccountResponse) + err := c.cc.Invoke(ctx, "/gmqtt.auth.api.AccountService/Get", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *accountServiceClient) Update(ctx context.Context, in *UpdateAccountRequest, opts ...grpc.CallOption) (*empty.Empty, error) { + out := new(empty.Empty) + err := c.cc.Invoke(ctx, "/gmqtt.auth.api.AccountService/Update", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *accountServiceClient) Delete(ctx context.Context, in *DeleteAccountRequest, opts ...grpc.CallOption) (*empty.Empty, error) { + out := new(empty.Empty) + err := c.cc.Invoke(ctx, "/gmqtt.auth.api.AccountService/Delete", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AccountServiceServer is the server API for AccountService service. +// All implementations must embed UnimplementedAccountServiceServer +// for forward compatibility +type AccountServiceServer interface { + // List all accounts + List(context.Context, *ListAccountsRequest) (*ListAccountsResponse, error) + // Get the account for given username. + // Return NotFound error when account not found. + Get(context.Context, *GetAccountRequest) (*GetAccountResponse, error) + // Update the password for the account. + // This API will create the account if not exists. + Update(context.Context, *UpdateAccountRequest) (*empty.Empty, error) + // Delete the account for given username + Delete(context.Context, *DeleteAccountRequest) (*empty.Empty, error) + mustEmbedUnimplementedAccountServiceServer() +} + +// UnimplementedAccountServiceServer must be embedded to have forward compatible implementations. +type UnimplementedAccountServiceServer struct { +} + +func (UnimplementedAccountServiceServer) List(context.Context, *ListAccountsRequest) (*ListAccountsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method List not implemented") +} +func (UnimplementedAccountServiceServer) Get(context.Context, *GetAccountRequest) (*GetAccountResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Get not implemented") +} +func (UnimplementedAccountServiceServer) Update(context.Context, *UpdateAccountRequest) (*empty.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Update not implemented") +} +func (UnimplementedAccountServiceServer) Delete(context.Context, *DeleteAccountRequest) (*empty.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Delete not implemented") +} +func (UnimplementedAccountServiceServer) mustEmbedUnimplementedAccountServiceServer() {} + +// UnsafeAccountServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AccountServiceServer will +// result in compilation errors. +type UnsafeAccountServiceServer interface { + mustEmbedUnimplementedAccountServiceServer() +} + +func RegisterAccountServiceServer(s grpc.ServiceRegistrar, srv AccountServiceServer) { + s.RegisterService(&_AccountService_serviceDesc, srv) +} + +func _AccountService_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListAccountsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AccountServiceServer).List(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gmqtt.auth.api.AccountService/List", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AccountServiceServer).List(ctx, req.(*ListAccountsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AccountService_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAccountRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AccountServiceServer).Get(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gmqtt.auth.api.AccountService/Get", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AccountServiceServer).Get(ctx, req.(*GetAccountRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AccountService_Update_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateAccountRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AccountServiceServer).Update(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gmqtt.auth.api.AccountService/Update", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AccountServiceServer).Update(ctx, req.(*UpdateAccountRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AccountService_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteAccountRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AccountServiceServer).Delete(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gmqtt.auth.api.AccountService/Delete", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AccountServiceServer).Delete(ctx, req.(*DeleteAccountRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _AccountService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "gmqtt.auth.api.AccountService", + HandlerType: (*AccountServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "List", + Handler: _AccountService_List_Handler, + }, + { + MethodName: "Get", + Handler: _AccountService_Get_Handler, + }, + { + MethodName: "Update", + Handler: _AccountService_Update_Handler, + }, + { + MethodName: "Delete", + Handler: _AccountService_Delete_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "account.proto", +} diff --git a/plugin/auth/auth.go b/plugin/auth/auth.go new file mode 100644 index 00000000..b9fbcacc --- /dev/null +++ b/plugin/auth/auth.go @@ -0,0 +1,174 @@ +package auth + +import ( + "context" + "crypto/md5" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "hash" + "io/ioutil" + "os" + "sync" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "go.uber.org/zap" + "golang.org/x/crypto/bcrypt" + "google.golang.org/grpc" + "gopkg.in/yaml.v2" + + "github.com/DrmagicE/gmqtt/config" + "github.com/DrmagicE/gmqtt/plugin/admin" + "github.com/DrmagicE/gmqtt/server" +) + +var _ server.Plugin = (*Auth)(nil) + +const Name = "auth" + +func init() { + server.RegisterPlugin(Name, New) + config.RegisterDefaultPluginConfig(Name, &DefaultConfig) +} + +func New(config config.Config) (server.Plugin, error) { + a := &Auth{ + config: config.Plugins[Name].(*Config), + indexer: admin.NewIndexer(), + } + a.saveFile = a.saveFileHandler + return a, nil +} + +var log *zap.Logger + +// Auth provides the username/password authentication for gmqtt. +// The authentication data is persist in config.PasswordFile. +type Auth struct { + config *Config + // gard indexer + mu sync.RWMutex + // store username/password + indexer *admin.Indexer + // saveFile persists the account data to password file. + saveFile func() error +} + +// generatePassword generates the hashed password for the plain password. +func (a *Auth) generatePassword(password string) (hashedPassword string, err error) { + var h hash.Hash + switch a.config.Hash { + case Plain: + return password, nil + case MD5: + h = md5.New() + case SHA256: + h = sha256.New() + case Bcrypt: + pwd, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost) + return string(pwd), err + default: + // just in case. + panic("invalid hash type") + } + _, err = h.Write([]byte(password)) + if err != nil { + return "", err + } + rs := h.Sum(nil) + return hex.EncodeToString(rs), nil +} + +func (a *Auth) mustEmbedUnimplementedAccountServiceServer() { + return +} + +func (a *Auth) validate(username, password string) (permitted bool, err error) { + a.mu.RLock() + elem := a.indexer.GetByID(username) + a.mu.RUnlock() + var hashedPassword string + if elem == nil { + return false, nil + } + ac := elem.Value.(*Account) + hashedPassword = ac.Password + if hashedPassword == "" || password == "" { + return true, nil + } + var h hash.Hash + switch a.config.Hash { + case Plain: + return hashedPassword == password, nil + case MD5: + h = md5.New() + case SHA256: + h = sha256.New() + case Bcrypt: + return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) == nil, nil + default: + // just in case. + panic("invalid hash type") + } + _, err = h.Write([]byte(password)) + if err != nil { + return false, err + } + rs := h.Sum(nil) + return hashedPassword == hex.EncodeToString(rs), nil +} + +func (a *Auth) RegisterGRPC(s grpc.ServiceRegistrar) { + RegisterAccountServiceServer(s, a) +} +func (a *Auth) RegisterHTTP(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + return RegisterAccountServiceHandlerFromEndpoint(ctx, mux, endpoint, opts) +} + +func (a *Auth) Load(service server.Server) error { + log = server.LoggerWithField(zap.String("plugin", Name)) + f, err := os.OpenFile(a.config.PasswordFile, os.O_CREATE|os.O_RDONLY, 0666) + if err != nil { + return err + } + defer f.Close() + b, err := ioutil.ReadAll(f) + if err != nil { + return err + } + var acts []*Account + err = yaml.Unmarshal(b, &acts) + if err != nil { + return err + } + log.Info("authentication data loaded", + zap.String("hash", a.config.Hash), + zap.Int("account_nums", len(acts)), + zap.String("password_file", a.config.PasswordFile)) + + dup := make(map[string]struct{}) + for _, v := range acts { + if v.Username == "" { + return errors.New("detect empty username in password file") + } + if _, ok := dup[v.Username]; ok { + return fmt.Errorf("detect duplicated username in password file: %s", v.Username) + } + dup[v.Username] = struct{}{} + } + a.mu.Lock() + defer a.mu.Unlock() + for _, v := range acts { + a.indexer.Set(v.Username, v) + } + return nil +} + +func (a *Auth) Unload() error { + return nil +} + +func (a *Auth) Name() string { + return Name +} diff --git a/plugin/auth/auth_test.go b/plugin/auth/auth_test.go new file mode 100644 index 00000000..e896e0d2 --- /dev/null +++ b/plugin/auth/auth_test.go @@ -0,0 +1,129 @@ +package auth + +import ( + "os" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + + "github.com/DrmagicE/gmqtt/config" + "github.com/DrmagicE/gmqtt/plugin/admin" + "github.com/DrmagicE/gmqtt/server" +) + +func TestAuth_validate(t *testing.T) { + var tt = []struct { + name string + username string + password string + }{ + { + name: Plain, + username: "user", + password: "道路千万条,安全第一条,密码不规范,绩效两行泪", + }, { + name: MD5, + username: "user", + password: "道路千万条,安全第一条,密码不规范,绩效两行泪", + }, { + name: SHA256, + username: "user", + password: "道路千万条,安全第一条,密码不规范,绩效两行泪", + }, { + name: Bcrypt, + username: "user", + password: "道路千万条,安全第一条,密码不规范,绩效两行泪", + }, + } + for _, v := range tt { + t.Run(v.name, func(t *testing.T) { + a := assert.New(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + auth := &Auth{ + config: &Config{ + Hash: v.name, + }, + indexer: admin.NewIndexer(), + } + + hashed, err := auth.generatePassword(v.password) + a.Nil(err) + auth.indexer.Set(v.username, &Account{ + Username: v.username, + Password: hashed, + }) + ok, err := auth.validate(v.username, v.password) + a.True(ok) + a.Nil(err) + }) + } + +} + +func TestAuth_Load_CreateFile(t *testing.T) { + a := assert.New(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + path := "./testdata/file_not_exists.yml" + defer os.Remove("./testdata/file_not_exists.yml") + cfg := DefaultConfig + cfg.PasswordFile = path + auth, err := New(config.Config{ + Plugins: map[string]config.Configuration{ + "auth": &cfg, + }, + }) + a.Nil(err) + ms := server.NewMockServer(ctrl) + a.Nil(auth.Load(ms)) +} + +func TestAuth_Load_WithDuplicatedUsername(t *testing.T) { + a := assert.New(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + path := "./testdata/gmqtt_password_duplicated.yml" + cfg := DefaultConfig + cfg.PasswordFile = path + cfg.Hash = Plain + auth, err := New(config.Config{ + Plugins: map[string]config.Configuration{ + "auth": &cfg, + }, + }) + a.Nil(err) + ms := server.NewMockServer(ctrl) + a.Error(auth.Load(ms)) +} + +func TestAuth_Load_OK(t *testing.T) { + a := assert.New(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + path := "./testdata/gmqtt_password.yml" + cfg := DefaultConfig + cfg.PasswordFile = path + cfg.Hash = Plain + auth, err := New(config.Config{ + Plugins: map[string]config.Configuration{ + "auth": &cfg, + }, + }) + a.Nil(err) + ms := server.NewMockServer(ctrl) + a.Nil(auth.Load(ms)) + + au := auth.(*Auth) + p, err := au.validate("u1", "p1") + a.True(p) + a.Nil(err) + + p, err = au.validate("u2", "p2") + a.True(p) + a.Nil(err) +} diff --git a/plugin/auth/config.go b/plugin/auth/config.go new file mode 100644 index 00000000..3979715b --- /dev/null +++ b/plugin/auth/config.go @@ -0,0 +1,75 @@ +package auth + +import ( + "errors" + "fmt" + "path" + + "github.com/mitchellh/go-homedir" +) + +func init() { + d, err := homedir.Dir() + if err != nil { + panic(fmt.Sprintf("cannot get home dir: %s", err)) + } + DefaultConfig.PasswordFile = path.Join(d, "gmqtt_password.yml") +} + +type hashType = string + +const ( + Plain hashType = "plain" + MD5 = "md5" + SHA256 = "sha256" + Bcrypt = "bcrypt" +) + +var ValidateHashType = []string{ + Plain, MD5, SHA256, Bcrypt, +} + +// Config is the configuration for the auth plugin. +type Config struct { + // PasswordFile is the file to store username and password. + PasswordFile string `yaml:"password_file"` + // Hash is the password hash algorithm. + // Possible values: plain | md5 | sha256 | bcrypt + Hash string `yaml:"hash"` +} + +// validate validates the configuration, and return an error if it is invalid. +func (c *Config) Validate() error { + if c.PasswordFile == "" { + return errors.New("password_file must be set") + } + for _, v := range ValidateHashType { + if v == c.Hash { + return nil + } + } + return fmt.Errorf("invalid hash type: %s", c.Hash) +} + +// DefaultConfig is the default configuration. +var DefaultConfig = Config{ + Hash: MD5, +} + +func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { + type cfg Config + var v = &struct { + Auth cfg `yaml:"auth"` + }{ + Auth: cfg(DefaultConfig), + } + if err := unmarshal(v); err != nil { + return err + } + empty := cfg(Config{}) + if v.Auth == empty { + v.Auth = cfg(DefaultConfig) + } + *c = Config(v.Auth) + return nil +} diff --git a/plugin/auth/grpc_handler.go b/plugin/auth/grpc_handler.go new file mode 100644 index 00000000..07ecebc5 --- /dev/null +++ b/plugin/auth/grpc_handler.go @@ -0,0 +1,151 @@ +package auth + +import ( + "bufio" + "container/list" + "context" + "io/ioutil" + "os" + + "github.com/golang/protobuf/ptypes/empty" + "go.uber.org/zap" + "gopkg.in/yaml.v2" + + "github.com/DrmagicE/gmqtt/plugin/admin" +) + +// List lists all accounts +func (a *Auth) List(ctx context.Context, req *ListAccountsRequest) (resp *ListAccountsResponse, err error) { + page, pageSize := admin.GetPage(req.Page, req.PageSize) + offset, n := admin.GetOffsetN(page, pageSize) + a.mu.RLock() + defer a.mu.RUnlock() + resp = &ListAccountsResponse{ + Accounts: []*Account{}, + TotalCount: 0, + } + a.indexer.Iterate(func(elem *list.Element) { + resp.Accounts = append(resp.Accounts, elem.Value.(*Account)) + }, offset, n) + + resp.TotalCount = uint32(a.indexer.Len()) + return resp, nil +} + +// Get gets the account for given username. +// Return NotFound error when account not found. +func (a *Auth) Get(ctx context.Context, req *GetAccountRequest) (resp *GetAccountResponse, err error) { + if req.Username == "" { + return nil, admin.ErrInvalidArgument("username", "cannot be empty") + } + a.mu.RLock() + defer a.mu.RUnlock() + resp = &GetAccountResponse{} + if e := a.indexer.GetByID(req.Username); e != nil { + resp.Account = e.Value.(*Account) + return resp, nil + } + return nil, admin.ErrNotFound +} + +// saveFileHandler is the default handler for auth.saveFile, must call after auth.mu is locked +func (a *Auth) saveFileHandler() error { + tmpfile, err := ioutil.TempFile("", "gmqtt_password") + if err != nil { + return err + } + defer tmpfile.Close() + + w := bufio.NewWriter(tmpfile) + // get all accounts + var accounts []*Account + a.indexer.Iterate(func(elem *list.Element) { + accounts = append(accounts, elem.Value.(*Account)) + }, 0, uint(a.indexer.Len())) + + b, err := yaml.Marshal(accounts) + if err != nil { + return err + } + + _, err = w.Write(b) + if err != nil { + return err + } + err = w.Flush() + if err != nil { + return err + } + // replace the old password file. + return os.Rename(tmpfile.Name(), a.config.PasswordFile) +} + +// Update updates the password for the account. +// Create a new account if the account for the username is not exists. +// Update will persist the account data to the password file. +func (a *Auth) Update(ctx context.Context, req *UpdateAccountRequest) (resp *empty.Empty, err error) { + if req.Username == "" { + return nil, admin.ErrInvalidArgument("username", "cannot be empty") + } + hashedPassword, err := a.generatePassword(req.Password) + if err != nil { + return &empty.Empty{}, err + } + a.mu.Lock() + defer a.mu.Unlock() + var oact *Account + elem := a.indexer.GetByID(req.Username) + if elem != nil { + oact = elem.Value.(*Account) + } + a.indexer.Set(req.Username, &Account{ + Username: req.Username, + Password: hashedPassword, + }) + err = a.saveFile() + if err != nil { + // should rollback if failed to persist to file. + if oact == nil { + a.indexer.Remove(req.Username) + return &empty.Empty{}, err + } + a.indexer.Set(req.Username, &Account{ + Username: req.Username, + Password: oact.Password, + }) + } + if oact == nil { + log.Info("new account created", zap.String("username", req.Username)) + } else { + log.Info("password updated", zap.String("username", req.Username)) + } + + return &empty.Empty{}, err +} + +// Delete deletes the account for the username. +func (a *Auth) Delete(ctx context.Context, req *DeleteAccountRequest) (resp *empty.Empty, err error) { + if req.Username == "" { + return nil, admin.ErrInvalidArgument("username", "cannot be empty") + } + a.mu.Lock() + defer a.mu.Unlock() + act := a.indexer.GetByID(req.Username) + if act == nil { + // fast path + return &empty.Empty{}, nil + } + oact := act.Value + a.indexer.Remove(req.Username) + err = a.saveFile() + if err != nil { + // should rollback if failed to persist to file + a.indexer.Set(req.Username, &Account{ + Username: req.Username, + Password: oact.(*Account).Password, + }) + return &empty.Empty{}, err + } + log.Info("account deleted", zap.String("username", req.Username)) + return &empty.Empty{}, nil +} diff --git a/plugin/auth/grpc_handler_test.go b/plugin/auth/grpc_handler_test.go new file mode 100644 index 00000000..c2c6892c --- /dev/null +++ b/plugin/auth/grpc_handler_test.go @@ -0,0 +1,210 @@ +package auth + +import ( + "context" + "errors" + "io/ioutil" + "testing" + + "github.com/stretchr/testify/assert" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "gopkg.in/yaml.v2" + + "github.com/DrmagicE/gmqtt/config" +) + +func TestAuth_List_Get_Delete(t *testing.T) { + a := assert.New(t) + path := "./testdata/gmqtt_password.yml" + cfg := DefaultConfig + cfg.PasswordFile = path + cfg.Hash = Plain + auth, err := New(config.Config{ + Plugins: map[string]config.Configuration{ + "auth": &cfg, + }, + }) + a.Nil(err) + err = auth.Load(nil) + a.Nil(err) + au := auth.(*Auth) + au.saveFile = func() error { + return nil + } + resp, err := au.List(context.Background(), &ListAccountsRequest{ + PageSize: 0, + Page: 0, + }) + a.Nil(err) + + a.EqualValues(2, resp.TotalCount) + a.Len(resp.Accounts, 2) + + act := make(map[string]string) + act["u1"] = "p1" + act["u2"] = "p2" + for _, v := range resp.Accounts { + a.Equal(act[v.Username], v.Password) + } + + getResp, err := au.Get(context.Background(), &GetAccountRequest{ + Username: "u1", + }) + a.Nil(err) + a.Equal("u1", getResp.Account.Username) + a.Equal("p1", getResp.Account.Password) + + _, err = au.Delete(context.Background(), &DeleteAccountRequest{ + Username: "u1", + }) + a.Nil(err) + + getResp, err = au.Get(context.Background(), &GetAccountRequest{ + Username: "u1", + }) + s, ok := status.FromError(err) + a.True(ok) + a.Equal(codes.NotFound, s.Code()) + +} + +func TestAuth_Update(t *testing.T) { + a := assert.New(t) + path := "./testdata/gmqtt_password.yml" + cfg := DefaultConfig + cfg.PasswordFile = path + cfg.Hash = Plain + auth, err := New(config.Config{ + Plugins: map[string]config.Configuration{ + "auth": &cfg, + }, + }) + a.Nil(err) + err = auth.Load(nil) + a.Nil(err) + au := auth.(*Auth) + au.saveFile = func() error { + return nil + } + _, err = au.Update(context.Background(), &UpdateAccountRequest{ + Username: "u1", + Password: "p2", + }) + a.Nil(err) + + l := au.indexer.GetByID("u1") + act := l.Value.(*Account) + a.Equal("p2", act.Password) + + // test rollback + au.saveFile = func() error { + return errors.New("some error") + } + _, err = au.Update(context.Background(), &UpdateAccountRequest{ + Username: "u1", + Password: "u3", + }) + a.NotNil(err) + l = au.indexer.GetByID("u1") + act = l.Value.(*Account) + // not change because fails to persist to password file. + a.Equal("p2", act.Password) + + _, err = au.Update(context.Background(), &UpdateAccountRequest{ + Username: "u10", + Password: "p3", + }) + a.NotNil(err) + // not exists because fails to persist to password file. + l = au.indexer.GetByID("u10") + a.Nil(l) + +} + +func TestAuth_Delete(t *testing.T) { + a := assert.New(t) + path := "./testdata/gmqtt_password.yml" + cfg := DefaultConfig + cfg.PasswordFile = path + cfg.Hash = Plain + auth, err := New(config.Config{ + Plugins: map[string]config.Configuration{ + "auth": &cfg, + }, + }) + a.Nil(err) + err = auth.Load(nil) + a.Nil(err) + au := auth.(*Auth) + au.saveFile = func() error { + return errors.New("some error") + } + _, err = au.Delete(context.Background(), &DeleteAccountRequest{ + Username: "u1", + }) + a.NotNil(err) + + resp, err := au.Get(context.Background(), &GetAccountRequest{ + Username: "u1", + }) + a.Nil(err) + a.Equal("u1", resp.Account.Username) + a.Equal("p1", resp.Account.Password) + + au.saveFile = func() error { + return nil + } + + _, err = au.Delete(context.Background(), &DeleteAccountRequest{ + Username: "u1", + }) + a.Nil(err) + + resp, err = au.Get(context.Background(), &GetAccountRequest{ + Username: "u1", + }) + s, ok := status.FromError(err) + a.True(ok) + a.Equal(codes.NotFound, s.Code()) +} + +func TestAuth_saveFileHandler(t *testing.T) { + a := assert.New(t) + path := "./testdata/gmqtt_password_save.yml" + originBytes, err := ioutil.ReadFile(path) + a.Nil(err) + defer func() { + // restore + ioutil.WriteFile(path, originBytes, 0666) + }() + cfg := DefaultConfig + cfg.PasswordFile = path + cfg.Hash = Plain + auth, err := New(config.Config{ + Plugins: map[string]config.Configuration{ + "auth": &cfg, + }, + }) + a.Nil(err) + err = auth.Load(nil) + a.Nil(err) + au := auth.(*Auth) + au.indexer.Set("u1", &Account{ + Username: "u1", + Password: "p11", + }) + au.indexer.Remove("u2") + err = au.saveFileHandler() + a.Nil(err) + b, err := ioutil.ReadFile(path) + a.Nil(err) + + var rs []*Account + err = yaml.Unmarshal(b, &rs) + a.Nil(err) + a.Len(rs, 1) + a.Equal("u1", rs[0].Username) + a.Equal("p11", rs[0].Password) + +} diff --git a/plugin/auth/hooks.go b/plugin/auth/hooks.go new file mode 100644 index 00000000..27f5ea4a --- /dev/null +++ b/plugin/auth/hooks.go @@ -0,0 +1,44 @@ +package auth + +import ( + "context" + + "go.uber.org/zap" + + "github.com/DrmagicE/gmqtt/pkg/codes" + "github.com/DrmagicE/gmqtt/pkg/packets" + "github.com/DrmagicE/gmqtt/server" +) + +func (a *Auth) HookWrapper() server.HookWrapper { + return server.HookWrapper{ + OnBasicAuthWrapper: a.OnBasicAuthWrapper, + } +} + +func (a *Auth) OnBasicAuthWrapper(pre server.OnBasicAuth) server.OnBasicAuth { + return func(ctx context.Context, client server.Client, req *server.ConnectRequest) (err error) { + err = pre(ctx, client, req) + if err != nil { + return err + } + ok, err := a.validate(string(req.Connect.Username), string(req.Connect.Password)) + if err != nil { + return err + } + if !ok { + log.Debug("authentication failed", zap.String("username", string(req.Connect.Username))) + switch client.Version() { + case packets.Version311: + return &codes.Error{ + Code: codes.V3NotAuthorized, + } + case packets.Version5: + return &codes.Error{ + Code: codes.NotAuthorized, + } + } + } + return nil + } +} diff --git a/plugin/auth/hooks_test.go b/plugin/auth/hooks_test.go new file mode 100644 index 00000000..5f0af3a1 --- /dev/null +++ b/plugin/auth/hooks_test.go @@ -0,0 +1,57 @@ +package auth + +import ( + "context" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + + "github.com/DrmagicE/gmqtt/config" + "github.com/DrmagicE/gmqtt/pkg/packets" + "github.com/DrmagicE/gmqtt/server" +) + +func TestAuth_OnBasicAuthWrapper(t *testing.T) { + a := assert.New(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + path := "./testdata/gmqtt_password.yml" + cfg := DefaultConfig + cfg.PasswordFile = path + cfg.Hash = Plain + auth, err := New(config.Config{ + Plugins: map[string]config.Configuration{ + "auth": &cfg, + }, + }) + mockClient := server.NewMockClient(ctrl) + mockClient.EXPECT().Version().Return(packets.Version311).AnyTimes() + a.Nil(err) + a.Nil(auth.Load(nil)) + au := auth.(*Auth) + var preCalled bool + fn := au.OnBasicAuthWrapper(func(ctx context.Context, client server.Client, req *server.ConnectRequest) (err error) { + preCalled = true + return nil + }) + // pass + a.Nil(fn(context.Background(), mockClient, &server.ConnectRequest{ + Connect: &packets.Connect{ + Username: []byte("u1"), + Password: []byte("p1"), + }, + })) + a.True(preCalled) + + // fail + a.NotNil(fn(context.Background(), mockClient, &server.ConnectRequest{ + Connect: &packets.Connect{ + Username: []byte("u1"), + Password: []byte("p11"), + }, + })) + + a.Nil(au.Unload()) +} diff --git a/plugin/auth/protos/account.proto b/plugin/auth/protos/account.proto new file mode 100644 index 00000000..d500a029 --- /dev/null +++ b/plugin/auth/protos/account.proto @@ -0,0 +1,70 @@ +syntax = "proto3"; + +package gmqtt.auth.api; +option go_package = ".;auth"; + +import "google/api/annotations.proto"; +import "google/protobuf/empty.proto"; + +message ListAccountsRequest { + uint32 page_size = 1; + uint32 page = 2; +} + +message ListAccountsResponse { + repeated Account accounts = 1; + uint32 total_count = 2; +} + +message GetAccountRequest { + string username = 1; +} + +message GetAccountResponse { + Account account =1; +} + +message UpdateAccountRequest { + string username = 1; + string password = 2; +} + +message Account { + string username = 1; + string password = 2; +} + +message DeleteAccountRequest { + string username = 1; +} + +service AccountService { + // List all accounts + rpc List (ListAccountsRequest) returns (ListAccountsResponse){ + option (google.api.http) = { + get: "/v1/accounts" + }; + } + + // Get the account for given username. + // Return NotFound error when account not found. + rpc Get (GetAccountRequest) returns (GetAccountResponse){ + option (google.api.http) = { + get: "/v1/accounts/{username}" + }; + } + // Update the password for the account. + // This API will create the account if not exists. + rpc Update(UpdateAccountRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v1/accounts/{username}" + body:"*" + }; + } + // Delete the account for given username + rpc Delete (DeleteAccountRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/v1/accounts/{username}" + }; + } +} diff --git a/plugin/auth/protos/proto_gen.sh b/plugin/auth/protos/proto_gen.sh new file mode 100755 index 00000000..261d433a --- /dev/null +++ b/plugin/auth/protos/proto_gen.sh @@ -0,0 +1,8 @@ +protoc -I. \ +-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway \ +-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ +--go-grpc_out=../ \ +--go_out=../ \ +--grpc-gateway_out=../ \ +--swagger_out=../swagger \ +*.proto \ No newline at end of file diff --git a/plugin/auth/swagger/account.swagger.json b/plugin/auth/swagger/account.swagger.json new file mode 100644 index 00000000..aa9f57a4 --- /dev/null +++ b/plugin/auth/swagger/account.swagger.json @@ -0,0 +1,231 @@ +{ + "swagger": "2.0", + "info": { + "title": "account.proto", + "version": "version not set" + }, + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/v1/accounts": { + "get": { + "summary": "List all accounts", + "operationId": "List", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/apiListAccountsResponse" + } + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/runtimeError" + } + } + }, + "parameters": [ + { + "name": "page_size", + "in": "query", + "required": false, + "type": "integer", + "format": "int64" + }, + { + "name": "page", + "in": "query", + "required": false, + "type": "integer", + "format": "int64" + } + ], + "tags": [ + "AccountService" + ] + } + }, + "/v1/accounts/{username}": { + "get": { + "summary": "Get the account for given username.\nReturn NotFound error when account not found.", + "operationId": "Get", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/apiGetAccountResponse" + } + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/runtimeError" + } + } + }, + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "AccountService" + ] + }, + "delete": { + "summary": "Delete the account for given username", + "operationId": "Delete", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "properties": {} + } + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/runtimeError" + } + } + }, + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "AccountService" + ] + }, + "post": { + "summary": "Update the password for the account.\nThis API will create the account if not exists.", + "operationId": "Update", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "properties": {} + } + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/runtimeError" + } + } + }, + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/apiUpdateAccountRequest" + } + } + ], + "tags": [ + "AccountService" + ] + } + } + }, + "definitions": { + "apiAccount": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "apiGetAccountResponse": { + "type": "object", + "properties": { + "account": { + "$ref": "#/definitions/apiAccount" + } + } + }, + "apiListAccountsResponse": { + "type": "object", + "properties": { + "accounts": { + "type": "array", + "items": { + "$ref": "#/definitions/apiAccount" + } + }, + "total_count": { + "type": "integer", + "format": "int64" + } + } + }, + "apiUpdateAccountRequest": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "protobufAny": { + "type": "object", + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "type": "string", + "format": "byte" + } + } + }, + "runtimeError": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/definitions/protobufAny" + } + } + } + } + } +} diff --git a/plugin/auth/testdata/gmqtt_password.yml b/plugin/auth/testdata/gmqtt_password.yml new file mode 100644 index 00000000..a6017fd1 --- /dev/null +++ b/plugin/auth/testdata/gmqtt_password.yml @@ -0,0 +1,4 @@ +- username: u1 + password: p1 +- username: u2 + password: p2 \ No newline at end of file diff --git a/plugin/auth/testdata/gmqtt_password_duplicated.yml b/plugin/auth/testdata/gmqtt_password_duplicated.yml new file mode 100644 index 00000000..e5471d57 --- /dev/null +++ b/plugin/auth/testdata/gmqtt_password_duplicated.yml @@ -0,0 +1,4 @@ +- username: u1 + password: p1 +- username: u1 + password: p1 \ No newline at end of file diff --git a/plugin/auth/testdata/gmqtt_password_save.yml b/plugin/auth/testdata/gmqtt_password_save.yml new file mode 100644 index 00000000..a6017fd1 --- /dev/null +++ b/plugin/auth/testdata/gmqtt_password_save.yml @@ -0,0 +1,4 @@ +- username: u1 + password: p1 +- username: u2 + password: p2 \ No newline at end of file diff --git a/server/plugin_mock.go b/server/plugin_mock.go index c8084394..2423c105 100644 --- a/server/plugin_mock.go +++ b/server/plugin_mock.go @@ -5,36 +5,35 @@ package server import ( - reflect "reflect" - gomock "github.com/golang/mock/gomock" + reflect "reflect" ) -// MockPlugable is a mock of Plugin interface -type MockPlugable struct { +// MockPlugin is a mock of Plugin interface +type MockPlugin struct { ctrl *gomock.Controller - recorder *MockPlugableMockRecorder + recorder *MockPluginMockRecorder } -// MockPlugableMockRecorder is the mock recorder for MockPlugable -type MockPlugableMockRecorder struct { - mock *MockPlugable +// MockPluginMockRecorder is the mock recorder for MockPlugin +type MockPluginMockRecorder struct { + mock *MockPlugin } -// NewMockPlugable creates a new mock instance -func NewMockPlugable(ctrl *gomock.Controller) *MockPlugable { - mock := &MockPlugable{ctrl: ctrl} - mock.recorder = &MockPlugableMockRecorder{mock} +// NewMockPlugin creates a new mock instance +func NewMockPlugin(ctrl *gomock.Controller) *MockPlugin { + mock := &MockPlugin{ctrl: ctrl} + mock.recorder = &MockPluginMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use -func (m *MockPlugable) EXPECT() *MockPlugableMockRecorder { +func (m *MockPlugin) EXPECT() *MockPluginMockRecorder { return m.recorder } // Load mocks base method -func (m *MockPlugable) Load(service Server) error { +func (m *MockPlugin) Load(service Server) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Load", service) ret0, _ := ret[0].(error) @@ -42,13 +41,13 @@ func (m *MockPlugable) Load(service Server) error { } // Load indicates an expected call of Load -func (mr *MockPlugableMockRecorder) Load(service interface{}) *gomock.Call { +func (mr *MockPluginMockRecorder) Load(service interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Load", reflect.TypeOf((*MockPlugable)(nil).Load), service) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Load", reflect.TypeOf((*MockPlugin)(nil).Load), service) } // Unload mocks base method -func (m *MockPlugable) Unload() error { +func (m *MockPlugin) Unload() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Unload") ret0, _ := ret[0].(error) @@ -56,13 +55,13 @@ func (m *MockPlugable) Unload() error { } // Unload indicates an expected call of Unload -func (mr *MockPlugableMockRecorder) Unload() *gomock.Call { +func (mr *MockPluginMockRecorder) Unload() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unload", reflect.TypeOf((*MockPlugable)(nil).Unload)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unload", reflect.TypeOf((*MockPlugin)(nil).Unload)) } // HookWrapper mocks base method -func (m *MockPlugable) HookWrapper() HookWrapper { +func (m *MockPlugin) HookWrapper() HookWrapper { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "HookWrapper") ret0, _ := ret[0].(HookWrapper) @@ -70,13 +69,13 @@ func (m *MockPlugable) HookWrapper() HookWrapper { } // HookWrapper indicates an expected call of HookWrapper -func (mr *MockPlugableMockRecorder) HookWrapper() *gomock.Call { +func (mr *MockPluginMockRecorder) HookWrapper() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HookWrapper", reflect.TypeOf((*MockPlugable)(nil).HookWrapper)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HookWrapper", reflect.TypeOf((*MockPlugin)(nil).HookWrapper)) } // Name mocks base method -func (m *MockPlugable) Name() string { +func (m *MockPlugin) Name() string { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Name") ret0, _ := ret[0].(string) @@ -84,7 +83,7 @@ func (m *MockPlugable) Name() string { } // Name indicates an expected call of Name -func (mr *MockPlugableMockRecorder) Name() *gomock.Call { +func (mr *MockPluginMockRecorder) Name() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockPlugable)(nil).Name)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockPlugin)(nil).Name)) } diff --git a/server/server.go b/server/server.go index 0e13b88f..a9c97087 100644 --- a/server/server.go +++ b/server/server.go @@ -33,7 +33,6 @@ var ( ErrInvalWsMsgType = errors.New("invalid websocket message type") statusPanic = "invalid server status" plugins = make(map[string]NewPlugin) - pluginOrder []string topicAliasMgrFactory = make(map[string]NewTopicAliasManager) persistenceFactories = make(map[string]NewPersistence) ) @@ -59,10 +58,6 @@ func RegisterPlugin(name string, new NewPlugin) { plugins[name] = new } -func SetPluginOrder(order []string) { - pluginOrder = order -} - // Server status const ( serverStatusInit = iota @@ -1010,7 +1005,7 @@ func (srv *server) initPluginHooks() error { onStopWrappers []OnStopWrapper onMsgDroppedWrappers []OnMsgDroppedWrapper ) - for _, v := range pluginOrder { + for _, v := range srv.config.PluginOrder { plg, err := plugins[v](srv.config) if err != nil { return err diff --git a/server/server_mock.go b/server/server_mock.go index 4b9737fd..10c28ebd 100644 --- a/server/server_mock.go +++ b/server/server_mock.go @@ -6,10 +6,9 @@ package server import ( context "context" - reflect "reflect" - config "github.com/DrmagicE/gmqtt/config" gomock "github.com/golang/mock/gomock" + reflect "reflect" ) // MockServer is a mock of Server interface @@ -145,16 +144,16 @@ func (mr *MockServerMockRecorder) RetainedService() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RetainedService", reflect.TypeOf((*MockServer)(nil).RetainedService)) } -// Plugin mocks base method -func (m *MockServer) Plugin(name string) Plugin { +// Plugins mocks base method +func (m *MockServer) Plugins() []Plugin { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Plugin", name) - ret0, _ := ret[0].(Plugin) + ret := m.ctrl.Call(m, "Plugins") + ret0, _ := ret[0].([]Plugin) return ret0 } -// Plugin indicates an expected call of Plugin -func (mr *MockServerMockRecorder) Plugin(name interface{}) *gomock.Call { +// Plugins indicates an expected call of Plugins +func (mr *MockServerMockRecorder) Plugins() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Plugin", reflect.TypeOf((*MockServer)(nil).Plugin), name) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Plugins", reflect.TypeOf((*MockServer)(nil).Plugins)) }