From b5ba3fb2ead2f9ef2fe42746826691ded51e83d0 Mon Sep 17 00:00:00 2001 From: Souhaila Noor Date: Thu, 2 Dec 2021 13:09:13 -0600 Subject: [PATCH] added auth arg validation and set up auth package --- auth/auth.go | 47 +++++++++++++++++++ ctl/server.go | 10 ++-- install/featurebase.conf | 9 ++-- server/auth.go | 35 -------------- server/config.go | 56 ++++++++++++++++++----- server/config_internal_test.go | 83 ++++++++++++++++++++++++++++++++++ server/server.go | 8 ++-- 7 files changed, 189 insertions(+), 59 deletions(-) create mode 100644 auth/auth.go delete mode 100644 server/auth.go diff --git a/auth/auth.go b/auth/auth.go new file mode 100644 index 000000000..95b222bf4 --- /dev/null +++ b/auth/auth.go @@ -0,0 +1,47 @@ +// Copyright 2017 Pilosa Corp. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package auth + +type AUTH struct { + ClientId string + ClientSecret string + AuthorizeURL string + TokenURL string + GroupEndpointURL string +} + +// func (c *config) Init(ClientId, ClientSecret, AuthorizeURL, TokenURL, GroupEndpointURL string) { +// c.ClientId = ClientId +// c.ClientSecret = ClientSecret +// c.AuthorizeURL = AuthorizeURL +// c.TokenURL = TokenURL +// c.GroupEndpointURL = GroupEndpointURL +// } + +// apiOption is a functional option type for pilosa.API +type authOption func(*AUTH) error + +func OptAuth(ClientId, ClientSecret, AuthorizeURL, TokenURL, GroupEndpointURL string) authOption { + return func(a *AUTH) error { + a.ClientId = ClientId + a.ClientSecret = ClientSecret + a.AuthorizeURL = AuthorizeURL + a.TokenURL = TokenURL + a.GroupEndpointURL = GroupEndpointURL + return nil + } +} + +// redirectURL diff --git a/ctl/server.go b/ctl/server.go index ac1dcd204..66a8c63d4 100644 --- a/ctl/server.go +++ b/ctl/server.go @@ -124,8 +124,10 @@ func BuildServerFlags(cmd *cobra.Command, srv *server.Command) { // OAuth2.0 identity provider configuration flags.BoolVar(&srv.Config.Auth.Enable, "auth.enable", false, "Enable AuthN/AuthZ of featurebase, disabled by default.") - flags.StringVar(&srv.Config.Auth.IdentityProviderURL, "auth.identity-provider-url", srv.Config.Auth.IdentityProviderURL, "Base URL for identity provider.") - flags.StringVar(&srv.Config.Auth.AuthorizeURL, "auth.authorize-url", srv.Config.Auth.AuthorizeURL, "Base URL for authorize.") - flags.StringVar(&srv.Config.Auth.UserInfoURL, "auth.user-info-url", srv.Config.Auth.UserInfoURL, "Base URL for user info.") - flags.StringVar(&srv.Config.Auth.ClientId, "auth.client-id", srv.Config.Auth.ClientId, "Application/Client ID") + flags.StringVar(&srv.Config.Auth.ClientId, "auth.client-id", srv.Config.Auth.ClientId, "Identity Provider's Application/Client ID.") + flags.StringVar(&srv.Config.Auth.ClientSecret, "auth.client-secret", srv.Config.Auth.ClientSecret, "Identity Provider's Application/Client Secret.") + flags.StringVar(&srv.Config.Auth.AuthorizeURL, "auth.authorize-url", srv.Config.Auth.AuthorizeURL, "Identity Provider's Authorize URL.") + flags.StringVar(&srv.Config.Auth.TokenURL, "auth.token-url", srv.Config.Auth.TokenURL, "Identity Provider's Token URL for identity provider.") + flags.StringVar(&srv.Config.Auth.GroupEndpointURL, "auth.group-endpoint-url", srv.Config.Auth.GroupEndpointURL, "Identity Provider's Group endpoint URL.") + } diff --git a/install/featurebase.conf b/install/featurebase.conf index d3fd41deb..7807134e7 100644 --- a/install/featurebase.conf +++ b/install/featurebase.conf @@ -375,7 +375,8 @@ log-path = "/var/log/molecula/featurebase.log" # Can choose identity provider, pass authorize and user-info endpoints, and client id # [auth] # enable = false -# identity-provider-url = "http://place-holder" -# authorize-url = "http://place-holder" -# user-info-url = "http://place-holder" -# client-id = "http://place-holder" \ No newline at end of file +# client-id = "" +# client-secret = "" +# authorize-url = "" +# token-url = "" +# group-endpoint-url = "" \ No newline at end of file diff --git a/server/auth.go b/server/auth.go deleted file mode 100644 index f74444963..000000000 --- a/server/auth.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2017 Pilosa Corp. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -type Options struct { - Enable bool `toml:"enable"` - IdentityProviderURL string `toml:"identity-provider-url"` - AuthorizeURL string `toml:"authorize-url"` - UserInfoURL string `toml:"user-info-url"` - ClientId string `toml:"client-id"` -} - -func authenticateUser(opt Options) (resp bool) { - - // fmt.Println("IdentityProviderURL", opt.IdentityProviderURL) - // fmt.Println("AuthorizeURL", opt.AuthorizeURL) - // fmt.Println("UserInfoURL", opt.UserInfoURL) - // fmt.Println("ClientId", opt.ClientId) - - resp = false - - return resp -} diff --git a/server/config.go b/server/config.go index c726cb5be..c65ae7f9c 100644 --- a/server/config.go +++ b/server/config.go @@ -19,6 +19,7 @@ import ( "fmt" "log" "net" + "net/url" "runtime" "strconv" "strings" @@ -246,17 +247,20 @@ type Config struct { // Enable AuthZ/AuthN for featurebase server Enable bool `toml:"enable"` - // Base URL for identity provider - IdentityProviderURL string `toml:"identity-provider-url"` + // Application/Client ID + ClientId string `toml:"client-id"` + + // Client Secret + ClientSecret string `toml:"client-secret"` // Authorize URL AuthorizeURL string `toml:"authorize-url"` - // User info URL - UserInfoURL string `toml:"user-info-url"` + // Token URL + TokenURL string `toml:"token-url"` - // Application/Client ID - ClientId string `toml:"client-id"` + // Group Endpoint URL + GroupEndpointURL string `toml:"group-endpoint-url"` } `toml:"auth"` } @@ -291,6 +295,7 @@ func (c *Config) validate() error { "Etcd.ClusterURL", c.Etcd.ClusterURL, "Postgres.Bind", c.Postgres.Bind, } + ports := make(map[int]bool) n := len(hostPort) for i := 0; i < n; i += 2 { @@ -411,12 +416,7 @@ func NewConfig() *Config { c.SchemaDetailsOn = true // AuthZ/AuthN disabled by default - // default identity provider is azure active directory c.Auth.Enable = false - c.Auth.IdentityProviderURL = "http://holder-identity-provider" - c.Auth.AuthorizeURL = "http://holder-authorize-url" - c.Auth.UserInfoURL = "http://holder-user-info-url" - c.Auth.ClientId = "http://holder-client-id" return c } @@ -628,3 +628,37 @@ func lookupAddr(ctx context.Context, resolver *net.Resolver, host string) (strin // No IPv4 address, return the first resolved address instead. return addrs[0].String(), nil } + +func (c *Config) ValidateAuth() error { + authURL := []string{ + "ClientId", c.Auth.ClientId, + "ClientSecret", c.Auth.ClientSecret, + "AuthorizeURL", c.Auth.AuthorizeURL, + "TokenURL", c.Auth.TokenURL, + "GroupEndpointURL", c.Auth.GroupEndpointURL, + } + + n := len(authURL) + for i := 0; i < n; i += 2 { + name := authURL[i] + value := authURL[i+1] + if strings.Contains(name, "URL") { + _, err := url.ParseRequestURI(value) + if err != nil { + return fmt.Errorf("Invalid URL for auth config %s: %s", name, err) + } + } else { + if value == "" { + return fmt.Errorf("Empty string for auth config %s", name) + } + } + } + return nil +} + +func (c *Config) MustValidateAuth() { + err := c.ValidateAuth() + if err != nil { + panic(err) + } +} diff --git a/server/config_internal_test.go b/server/config_internal_test.go index f48db1a16..97b1cfa20 100644 --- a/server/config_internal_test.go +++ b/server/config_internal_test.go @@ -288,3 +288,86 @@ func TestConfig_validateAddrsGRPC(t *testing.T) { }) } } + +type params struct { + enable bool + clientId string + clientSecret string + authorizeURL string + tokenURL string + groupEndpointURL string +} + +func TestConfig_validateAuth(t *testing.T) { + tests := []struct { + expErr string + input params + expected params + }{ + {"Empty string for auth config ClientId", + params{true, "", "", "", "", ""}, + params{true, "clientidstring", "clientSecret", "https://url.com/", "https://url.com/", "https://url.com/"}, + }, + {"Empty string for auth config ClientSecret", + params{true, "clientid", "", "", "", ""}, + params{true, "clientidstring", "clientSecret", "https://url.com/", "https://url.com/", "https://url.com/"}, + }, + {"Empty string for auth config ClientId", + params{true, "", "clientSecret", "", "", ""}, + params{true, "clientidstring", "clientSecret", "https://url.com/", "https://url.com/", "https://url.com/"}, + }, + {"Invalid URL for auth config AuthorizeURL", + params{true, "client", "secret", "", "", ""}, + params{true, "clientidstring", "clientSecret", "https://url.com/", "https://url.com/", "https://url.com/"}, + }, + {"Invalid URL for auth config TokenURL", + params{true, "client", "secret", "https://url.com/", "", ""}, + params{true, "clientidstring", "clientSecret", "https://url.com/", "https://url.com/", "https://url.com/"}, + }, + {"Invalid URL for auth config GroupEndpointURL", + params{true, "client", "secret", "https://url.com/", "https://url.com/", ""}, + params{true, "clientidstring", "clientSecret", "https://url.com/", "https://url.com/", "https://url.com/"}, + }, + {"Invalid URL for auth config AuthorizeURL", + params{true, "client", "secret", "string", "https://url.com/", "https://url.com/"}, + params{true, "clientidstring", "clientSecret", "https://url.com/", "https://url.com/", "https://url.com/"}, + }, + {"Invalid URL for auth config TokenURL", + params{true, "client", "secret", "https://url.com/", "not-a-url", ""}, + params{true, "clientidstring", "clientSecret", "https://url.com/", "https://url.com/", "https://url.com/"}, + }, + {"Invalid URL for auth config GroupEndpointURL", + params{true, "client", "secret", "https://url.com/", "https://url.com/", "not-valid-url"}, + params{true, "clientidstring", "clientSecret", "https://url.com/", "https://url.com/", "https://url.com/"}, + }, + {"", + params{true, "client", "secret", "https://url.com/", "https://url.com/", "https://url.com/"}, + params{true, "clientidstring", "clientSecret", "https://url.com/", "https://url.com/", "https://url.com/"}, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + c := NewConfig() + c.Auth.Enable = test.input.enable + c.Auth.ClientId = test.input.clientId + c.Auth.ClientSecret = test.input.clientSecret + c.Auth.AuthorizeURL = test.input.authorizeURL + c.Auth.TokenURL = test.input.tokenURL + c.Auth.GroupEndpointURL = test.input.groupEndpointURL + + err := c.ValidateAuth() + + if err != nil && test.expErr == "" { + t.Fatal(err) + } else if err == nil && test.expErr != "" { + t.Fatalf("expected error string to contain %s, but got no error", test.expErr) + } else if err != nil && test.expErr != "" { + if !strings.Contains(err.Error(), test.expErr) { + t.Fatalf("expected error string to contain %s, but got %s", test.expErr, err.Error()) + } + return + } + }) + } +} diff --git a/server/server.go b/server/server.go index e24c261cb..30b1794ee 100644 --- a/server/server.go +++ b/server/server.go @@ -41,6 +41,7 @@ import ( "golang.org/x/sync/errgroup" pilosa "github.com/molecula/featurebase/v2" + "github.com/molecula/featurebase/v2/auth" "github.com/molecula/featurebase/v2/boltdb" "github.com/molecula/featurebase/v2/encoding/proto" petcd "github.com/molecula/featurebase/v2/etcd" @@ -235,11 +236,8 @@ func (m *Command) Start() (err error) { } if m.Config.Auth.Enable == true { - // check authentication for user - resp := authenticateUser(m.Config.Auth) - if resp == false { - log.Fatalf("Authentication failed: Unable to access to featurebase server") - } + m.Config.MustValidateAuth() + auth.OptAuth(m.Config.Auth.ClientId, m.Config.Auth.ClientSecret, m.Config.Auth.AuthorizeURL, m.Config.Auth.TokenURL, m.Config.Auth.GroupEndpointURL) } // Initialize server.