From 5b62727bb50f60c3580bfbbfcaee443902d434d0 Mon Sep 17 00:00:00 2001 From: jandelgado Date: Wed, 21 Apr 2021 09:02:28 +0200 Subject: [PATCH] clean up names --- README.md | 30 ++++++++++---------- cmd/example/demo_auth.go | 31 +++++++++++++++++++++ cmd/example/demo_authenticator.go | 31 --------------------- cmd/example/log_intercepting_auth.go | 30 ++++++++++---------- cmd/example/main.go | 16 +++++------ pkg/auth.go | 10 +++---- pkg/{serve.go => service.go} | 38 ++++++++++++++------------ pkg/{serve_test.go => service_test.go} | 24 ++++++++-------- 8 files changed, 107 insertions(+), 103 deletions(-) create mode 100644 cmd/example/demo_auth.go delete mode 100644 cmd/example/demo_authenticator.go rename pkg/{serve.go => service.go} (51%) rename pkg/{serve_test.go => service_test.go} (83%) diff --git a/README.md b/README.md index 12da5ac..bd0403b 100644 --- a/README.md +++ b/README.md @@ -19,14 +19,16 @@ For details see https://github.com/rabbitmq/rabbitmq-server/tree/master/deps/rab ## Build your own service -To build an RabbitMQ HTTP Auth Backend, you just need to implement the provided -`Authenticator` interface, which will be called by `POST` requests to the paths +To build a RabbitMQ HTTP Auth Backend, you just need to implement the provided +`Auth` interface, which will be called by `POST` requests to the paths `/auth/user`, `/auth/vhost`, `/auth/topic` and `/auth/resource`: ```go +package rabbitmqauth + type Decision bool -type Authenticator interface { +type Auth interface { // User authenticates the given user. In addition to the decision, the tags // associated with the user are returned. User(username, password string) (Decision, string) @@ -41,8 +43,8 @@ type Authenticator interface { } ``` -Start a web server using your authenticator and the http router provided -by the `rabbitmqauth.AuthServer.NewRouter()` function like: +Start a web server using your Auth implementation and the http router provided +by the `rabbitmqauth.AuthService.NewRouter()` function like: ```go package main @@ -52,24 +54,24 @@ import ( "net/http" "time" - auth "github.com/jandelgado/rabbitmq-http-auth/pkg" + rabbitmqauth "github.com/jandelgado/rabbitmq-http-auth/pkg" ) const httpReadTimeout = 10 * time.Second const httpWriteTimeout = 10 * time.Second func main() { - authenticator := NewLogInterceptingAuthenticator(DemoAuthenticator{}) - s := auth.NewAuthServer(authenticator) + auth := NewLogInterceptingAuth(DemoAuth{}) + service := rabbitmqauth.NewAuthService(auth) - srv := &http.Server{ - Handler: s.NewRouter(), + server := &http.Server{ + Handler: service.NewRouter(), Addr: fmt.Sprintf(":%d", 8000), WriteTimeout: httpWriteTimeout, ReadTimeout: httpReadTimeout, } - err := srv.ListenAndServe() + err := server.ListenAndServe() if err != nil { panic(err) @@ -91,7 +93,7 @@ $ curl -XPOST localhost:8000/auth/user -d "username=john&password=test" deny ``` -Since the `DemoAuthenticator` only allows the `guest` user (but with any +Since the `DemoAuth` only allows the `guest` user (but with any password), this is the expected result. ## Test with RabbitMQ @@ -108,12 +110,12 @@ Then in another console, try to publish a message using [rabtap](TODO) $ echo "hello" | rabtap pub --uri amqp://guest:123@localhost:5672 --exchange amq.topic --routingkey "#" ``` -In the docker-compose log, should see the authenticator logging the request: +In the docker-compose log, should see the auth server logging the request: ``` auth-http_1 | 2021/04/18 21:28:01 auth user(u=guest) -> allow [management administrator demo] ``` -As the `DemoAuthenticator` allows any password for the guest user, you can +As the `DemoAuth` allows any password for the guest user, you can try to change the password in the `rabtap` command or try to login on the [management console](http://localhost:15672) with any password. diff --git a/cmd/example/demo_auth.go b/cmd/example/demo_auth.go new file mode 100644 index 0000000..85804ba --- /dev/null +++ b/cmd/example/demo_auth.go @@ -0,0 +1,31 @@ +// rabbitmq-http-auth - demo implementation of the Auth interface, +// which allows the user guest with ANY password to be authorized for +// everything. +// (c) copyright 2021 by Jan Delgado +package main + +import ( + rabbitmqauth "github.com/jandelgado/rabbitmq-http-auth/pkg" +) + +type DemoAuth struct{} + +func (s DemoAuth) String() string { + return "DemoAuth" +} + +func (s DemoAuth) Resource(username, vhost, resource, name, permission string) rabbitmqauth.Decision { + return rabbitmqauth.Allow +} + +func (s DemoAuth) User(username, password string) (rabbitmqauth.Decision, string) { + return username == "guest", "management administrator demo" +} + +func (s DemoAuth) VHost(username, vhost, ip string) rabbitmqauth.Decision { + return rabbitmqauth.Allow +} + +func (s DemoAuth) Topic(username, vhost, resource, name, permission, routingKey string) rabbitmqauth.Decision { + return rabbitmqauth.Allow +} diff --git a/cmd/example/demo_authenticator.go b/cmd/example/demo_authenticator.go deleted file mode 100644 index 68a8fb8..0000000 --- a/cmd/example/demo_authenticator.go +++ /dev/null @@ -1,31 +0,0 @@ -// rabbitmq-http-auth - demo implementation of the Authenticator interface, -// which allows the user guest with ANY password to be authorized for -// everything. -// (c) copyright 2021 by Jan Delgado -package main - -import ( - auth "github.com/jandelgado/rabbitmq-http-auth/pkg" -) - -type DemoAuthenticator struct{} - -func (s DemoAuthenticator) String() string { - return "DemoAuthenticator" -} - -func (s DemoAuthenticator) Resource(username, vhost, resource, name, permission string) auth.Decision { - return true -} - -func (s DemoAuthenticator) User(username, password string) (auth.Decision, string) { - return username == "guest", "management administrator demo" -} - -func (s DemoAuthenticator) VHost(username, vhost, ip string) auth.Decision { - return true -} - -func (s DemoAuthenticator) Topic(username, vhost, resource, name, permission, routingKey string) auth.Decision { - return true -} diff --git a/cmd/example/log_intercepting_auth.go b/cmd/example/log_intercepting_auth.go index 4d9fea2..e942cc3 100644 --- a/cmd/example/log_intercepting_auth.go +++ b/cmd/example/log_intercepting_auth.go @@ -5,42 +5,42 @@ package main import ( "log" - auth "github.com/jandelgado/rabbitmq-http-auth/pkg" + rabbitmqauth "github.com/jandelgado/rabbitmq-http-auth/pkg" ) -type LogInterceptingAuthenticator struct { - authenticator auth.Authenticator +type LogInterceptingAuth struct { + auth rabbitmqauth.Auth } -func NewLogInterceptingAuthenticator(authenticator auth.Authenticator) auth.Authenticator { - return LogInterceptingAuthenticator{authenticator} +func NewLogInterceptingAuth(auth rabbitmqauth.Auth) rabbitmqauth.Auth { + return LogInterceptingAuth{auth} } -func (s LogInterceptingAuthenticator) String() string { - return "LogInterceptingAuthenticator" +func (s LogInterceptingAuth) String() string { + return "LogInterceptingAuth" } -func (s LogInterceptingAuthenticator) User(username, password string) (auth.Decision, string) { - res, tags := s.authenticator.User(username, password) +func (s LogInterceptingAuth) User(username, password string) (rabbitmqauth.Decision, string) { + res, tags := s.auth.User(username, password) log.Printf("auth user(u=%s) -> %v [%s]", username, res, tags) return res, tags } -func (s LogInterceptingAuthenticator) VHost(username, vhost, ip string) auth.Decision { - res := s.authenticator.VHost(username, vhost, ip) +func (s LogInterceptingAuth) VHost(username, vhost, ip string) rabbitmqauth.Decision { + res := s.auth.VHost(username, vhost, ip) log.Printf("auth vhost(u=%s,v=%s,i=%s) -> %v", username, vhost, ip, res) return res } -func (s LogInterceptingAuthenticator) Resource(username, vhost, resource, name, permission string) auth.Decision { - res := s.authenticator.Resource(username, vhost, resource, name, permission) +func (s LogInterceptingAuth) Resource(username, vhost, resource, name, permission string) rabbitmqauth.Decision { + res := s.auth.Resource(username, vhost, resource, name, permission) log.Printf("auth resource(u=%s,v=%s,r=%s,n=%s,p=%s) -> %v", username, vhost, resource, name, permission, res) return res } -func (s LogInterceptingAuthenticator) Topic(username, vhost, resource, name, permission, routingKey string) auth.Decision { - res := s.authenticator.Topic(username, vhost, resource, name, permission, routingKey) +func (s LogInterceptingAuth) Topic(username, vhost, resource, name, permission, routingKey string) rabbitmqauth.Decision { + res := s.auth.Topic(username, vhost, resource, name, permission, routingKey) log.Printf("auth topic(u=%s,v=%s,r=%s,n=%s,p=%s,k=%s) -> %v", username, vhost, resource, name, permission, routingKey, res) return res diff --git a/cmd/example/main.go b/cmd/example/main.go index 53dfa92..a2a89c8 100644 --- a/cmd/example/main.go +++ b/cmd/example/main.go @@ -1,5 +1,5 @@ -// rabbitmq-http-auth - exmple authentication service using a demo authenticator -// (c) copyright 2021 by Jan Delgadoauthenticator} +// rabbitmq-http-auth - exmaple rabbitmq http auth backend service +// (c) copyright 2021 by Jan Delgado package main import ( @@ -7,24 +7,24 @@ import ( "net/http" "time" - auth "github.com/jandelgado/rabbitmq-http-auth/pkg" + rabbitmqauth "github.com/jandelgado/rabbitmq-http-auth/pkg" ) const httpReadTimeout = 10 * time.Second const httpWriteTimeout = 10 * time.Second func main() { - authenticator := NewLogInterceptingAuthenticator(DemoAuthenticator{}) - s := auth.NewAuthServer(authenticator) + auth := NewLogInterceptingAuth(DemoAuth{}) + service := rabbitmqauth.NewAuthService(auth) - srv := &http.Server{ - Handler: s.NewRouter(), + server := &http.Server{ + Handler: service.NewRouter(), Addr: fmt.Sprintf(":%d", 8000), WriteTimeout: httpWriteTimeout, ReadTimeout: httpReadTimeout, } - err := srv.ListenAndServe() + err := server.ListenAndServe() if err != nil { panic(err) diff --git a/pkg/auth.go b/pkg/auth.go index af2f6e7..659828b 100644 --- a/pkg/auth.go +++ b/pkg/auth.go @@ -1,4 +1,4 @@ -// rabbitmq-http-auth - Authenicator interface +// rabbitmq-http-auth - AuthN/AuthZ interface // (c) copyright 2021 by Jan Delgado package rabbitmqauth @@ -9,13 +9,13 @@ const ( Deny Decision = false ) -// Authenticator instances make the actual decisions on authentication -// requests by RabboitMQ. Every function returns the authentication decision, -// which is always Allow or Deny. +// Auth instances make the actual decisions on authentication and authorization +// requests by RabboitMQ. Every function returns the decision, which is always +// Allow or Deny. // // See https://github.com/rabbitmq/rabbitmq-server/tree/master/deps/rabbitmq_auth_backend_http // for a detailed description. -type Authenticator interface { +type Auth interface { // User authenticates the given user. In addition to the decision, the tags // associated with the user are returned. User(username, password string) (Decision, string) diff --git a/pkg/serve.go b/pkg/service.go similarity index 51% rename from pkg/serve.go rename to pkg/service.go index 4a31d9c..3a5aa6c 100644 --- a/pkg/serve.go +++ b/pkg/service.go @@ -7,12 +7,12 @@ import ( "net/http" ) -type AuthServer struct { - authenticator Authenticator +type AuthService struct { + auth Auth } -func NewAuthServer(authenticator Authenticator) AuthServer { - return AuthServer{authenticator} +func NewAuthService(auth Auth) AuthService { + return AuthService{auth} } func (s Decision) String() string { @@ -22,7 +22,7 @@ func (s Decision) String() string { return "deny" } -func validatePostArgs(args []string, r *http.Request) map[string]string { +func postArgsToMap(args []string, r *http.Request) map[string]string { result := map[string]string{} for _, s := range args { result[s] = r.PostFormValue(s) @@ -30,9 +30,9 @@ func validatePostArgs(args []string, r *http.Request) map[string]string { return result } -func (s *AuthServer) userHandler(w http.ResponseWriter, r *http.Request) { - args := validatePostArgs([]string{"username", "password"}, r) - res, tags := s.authenticator.User(args["username"], args["password"]) +func (s *AuthService) userHandler(w http.ResponseWriter, r *http.Request) { + args := postArgsToMap([]string{"username", "password"}, r) + res, tags := s.auth.User(args["username"], args["password"]) if res { fmt.Fprintf(w, "%s [%s]", res, tags) } else { @@ -40,26 +40,28 @@ func (s *AuthServer) userHandler(w http.ResponseWriter, r *http.Request) { } } -func (s *AuthServer) vhostHandler(w http.ResponseWriter, r *http.Request) { - args := validatePostArgs([]string{"username", "vhost", "ip"}, r) - res := s.authenticator.VHost(args["username"], args["vhost"], args["ip"]) +func (s *AuthService) vhostHandler(w http.ResponseWriter, r *http.Request) { + args := postArgsToMap([]string{"username", "vhost", "ip"}, r) + res := s.auth.VHost(args["username"], args["vhost"], args["ip"]) fmt.Fprintf(w, "%s", res) } -func (s *AuthServer) topicHandler(w http.ResponseWriter, r *http.Request) { - args := validatePostArgs([]string{"username", "vhost", "resource", "name", "permission", "routing_key"}, r) - res := s.authenticator.Topic(args["username"], args["vhost"], +func (s *AuthService) topicHandler(w http.ResponseWriter, r *http.Request) { + args := postArgsToMap([]string{"username", "vhost", "resource", "name", "permission", "routing_key"}, r) + res := s.auth.Topic(args["username"], args["vhost"], args["resource"], args["name"], args["permission"], args["routing_key"]) fmt.Fprintf(w, "%s", res) } -func (s *AuthServer) resourceHandler(w http.ResponseWriter, r *http.Request) { - args := validatePostArgs([]string{"username", "vhost", "resource", "name", "permission"}, r) - res := s.authenticator.Resource(args["username"], args["vhost"], +func (s *AuthService) resourceHandler(w http.ResponseWriter, r *http.Request) { + args := postArgsToMap([]string{"username", "vhost", "resource", "name", "permission"}, r) + res := s.auth.Resource(args["username"], args["vhost"], args["resource"], args["name"], args["permission"]) fmt.Fprintf(w, "%s", res) } +// postHandler passes a request to the given handler only if it is a POST +// request, otherwise returning a 405 func postHandler(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { @@ -71,7 +73,7 @@ func postHandler(handler func(http.ResponseWriter, *http.Request)) func(http.Res } } -func (s *AuthServer) NewRouter() http.Handler { +func (s *AuthService) NewRouter() http.Handler { router := http.NewServeMux() router.HandleFunc("/auth/user", postHandler(s.userHandler)) router.HandleFunc("/auth/vhost", postHandler(s.vhostHandler)) diff --git a/pkg/serve_test.go b/pkg/service_test.go similarity index 83% rename from pkg/serve_test.go rename to pkg/service_test.go index df8568a..88ac753 100644 --- a/pkg/serve_test.go +++ b/pkg/service_test.go @@ -11,25 +11,25 @@ import ( "github.com/stretchr/testify/require" ) -type MockAuthenticator struct{} +type MockAuth struct{} -func (s MockAuthenticator) String() string { - return "MockAuthenticator" +func (s MockAuth) String() string { + return "MockAuth" } -func (s MockAuthenticator) User(username, password string) (Decision, string) { +func (s MockAuth) User(username, password string) (Decision, string) { return username == "a" && password == "b", "management" } -func (s MockAuthenticator) VHost(username, vhost, ip string) Decision { +func (s MockAuth) VHost(username, vhost, ip string) Decision { return username == "a" && vhost == "b" && ip == "c" } -func (s MockAuthenticator) Resource(username, vhost, resource, name, permission string) Decision { +func (s MockAuth) Resource(username, vhost, resource, name, permission string) Decision { return username == "a" && vhost == "b" && resource == "c" && name == "d" && permission == "e" } -func (s MockAuthenticator) Topic(username, vhost, resource, name, permission, routingKey string) Decision { +func (s MockAuth) Topic(username, vhost, resource, name, permission, routingKey string) Decision { return username == "a" && vhost == "b" && resource == "c" && name == "d" && permission == "e" && routingKey == "f" } @@ -50,7 +50,7 @@ func TestPostHandlerReturns405OnGET(t *testing.T) { cases := []string{"/auth/user", "/auth/vhost", "/auth/resource", "/auth/topic"} for _, path := range cases { - auth := NewAuthServer(MockAuthenticator{}) + auth := NewAuthService(MockAuth{}) handlerFunc := auth.NewRouter() req, err := http.NewRequest("GET", path, nil) @@ -65,7 +65,7 @@ func TestPostHandlerReturns405OnGET(t *testing.T) { } func TestUserRequestIsAllowedOnlyOnValidRequests(t *testing.T) { - auth := NewAuthServer(MockAuthenticator{}) + auth := NewAuthService(MockAuth{}) srv := httptest.NewServer(auth.NewRouter()) defer srv.Close() @@ -86,7 +86,7 @@ func TestUserRequestIsAllowedOnlyOnValidRequests(t *testing.T) { } func TestVHostRequestIsAllowedOnlyOnValidRequests(t *testing.T) { - auth := NewAuthServer(MockAuthenticator{}) + auth := NewAuthService(MockAuth{}) srv := httptest.NewServer(auth.NewRouter()) defer srv.Close() @@ -107,7 +107,7 @@ func TestVHostRequestIsAllowedOnlyOnValidRequests(t *testing.T) { } func TestResourceRequestIsAllowedOnlyOnValidRequests(t *testing.T) { - auth := NewAuthServer(MockAuthenticator{}) + auth := NewAuthService(MockAuth{}) srv := httptest.NewServer(auth.NewRouter()) defer srv.Close() @@ -128,7 +128,7 @@ func TestResourceRequestIsAllowedOnlyOnValidRequests(t *testing.T) { } func TestTopicRequestIsAllowedOnlyOnValidRequests(t *testing.T) { - auth := NewAuthServer(MockAuthenticator{}) + auth := NewAuthService(MockAuth{}) srv := httptest.NewServer(auth.NewRouter()) defer srv.Close()