mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-10-07 11:27:50 +00:00
Merge pull request #1805 from molecula/fb1000
FB-1000: Ability to map user groups to cluster admin / index-level permissions
This commit is contained in:
commit
7d0e0600d6
9 changed files with 692 additions and 154 deletions
25
auth/auth.go
25
auth/auth.go
|
|
@ -1,25 +0,0 @@
|
|||
// Copyright 2021 Molecula Corp. All rights reserved.
|
||||
package auth
|
||||
|
||||
type Auth struct {
|
||||
// Enable AuthZ/AuthN for featurebase server
|
||||
Enable bool `toml:"enable"`
|
||||
|
||||
// Application/Client ID
|
||||
ClientId string `toml:"client-id"`
|
||||
|
||||
// Client Secret
|
||||
ClientSecret string `toml:"client-secret"`
|
||||
|
||||
// Authorize URL
|
||||
AuthorizeURL string `toml:"authorize-url"`
|
||||
|
||||
// Token URL
|
||||
TokenURL string `toml:"token-url"`
|
||||
|
||||
// Group Endpoint URL
|
||||
GroupEndpointURL string `toml:"group-endpoint-url"`
|
||||
|
||||
// Scope URL
|
||||
ScopeURL string `toml:"scope-url"`
|
||||
}
|
||||
150
authz/authorization.go
Normal file
150
authz/authorization.go
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
// 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 authz
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type Auth struct {
|
||||
// Enable AuthZ/AuthN for featurebase server
|
||||
Enable bool `toml:"enable"`
|
||||
|
||||
// Application/Client ID
|
||||
ClientId string `toml:"client-id"`
|
||||
|
||||
// Client Secret
|
||||
ClientSecret string `toml:"client-secret"`
|
||||
|
||||
// Authorize URL
|
||||
AuthorizeURL string `toml:"authorize-url"`
|
||||
|
||||
// Token URL
|
||||
TokenURL string `toml:"token-url"`
|
||||
|
||||
// Group Endpoint URL
|
||||
GroupEndpointURL string `toml:"group-endpoint-url"`
|
||||
|
||||
// Scope URL
|
||||
ScopeURL string `toml:"scope-url"`
|
||||
|
||||
// Permissions file for groups
|
||||
PermissionsFile string `toml:"permissions"`
|
||||
}
|
||||
|
||||
type GroupPermissions struct {
|
||||
Permissions map[string]map[string]string `yaml:"user-groups"`
|
||||
Admin string `yaml:"admin"`
|
||||
}
|
||||
|
||||
type Group struct {
|
||||
UserID string
|
||||
GroupID string `json:"id"`
|
||||
GroupName string `json:"displayName"`
|
||||
}
|
||||
|
||||
func (p *GroupPermissions) ReadPermissionsFile(permsFile io.Reader) (err error) {
|
||||
permsData, err := ioutil.ReadAll(permsFile)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading permissions failed with error: %s", err)
|
||||
}
|
||||
|
||||
err = yaml.UnmarshalStrict(permsData, &p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshalling permissions failed with error: %s", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *GroupPermissions) GetPermissions(groups []Group, index string) (permission string, errors error) {
|
||||
|
||||
if admin := p.IsAdmin(groups); admin {
|
||||
return "admin", nil
|
||||
}
|
||||
|
||||
allPermissions := map[string]bool{
|
||||
"write": false,
|
||||
"read": false,
|
||||
}
|
||||
|
||||
if len(groups) == 0 {
|
||||
return "", fmt.Errorf("user is not part of any groups in identity provider")
|
||||
}
|
||||
|
||||
var groupsDenied []string
|
||||
for _, group := range groups {
|
||||
if _, ok := p.Permissions[group.GroupID]; ok {
|
||||
if perm, ok := p.Permissions[group.GroupID][index]; ok {
|
||||
allPermissions[perm] = true
|
||||
} else {
|
||||
return "", fmt.Errorf("user %s does not have permission to index %s", group.UserID, index)
|
||||
}
|
||||
} else {
|
||||
groupsDenied = append(groupsDenied, group.GroupID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(groupsDenied) == len(groups) {
|
||||
return "", fmt.Errorf("group(s) %s does not have permission to FeatureBase", groupsDenied)
|
||||
}
|
||||
|
||||
if allPermissions["write"] {
|
||||
return "write", nil
|
||||
} else if allPermissions["read"] {
|
||||
return "read", nil
|
||||
} else {
|
||||
return "", fmt.Errorf("no permissions found")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *GroupPermissions) IsAdmin(groups []Group) bool {
|
||||
for _, group := range groups {
|
||||
if p.Admin == group.GroupID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *GroupPermissions) GetAuthorizedIndexList(groups []Group, desiredPermission string) (indexList []string) {
|
||||
// if user is admin, find all indexes in permissions file and return them
|
||||
if admin := p.IsAdmin(groups); admin {
|
||||
for groupId := range p.Permissions {
|
||||
for index := range p.Permissions[groupId] {
|
||||
indexList = append(indexList, index)
|
||||
}
|
||||
}
|
||||
return indexList
|
||||
}
|
||||
|
||||
for _, group := range groups {
|
||||
if _, ok := p.Permissions[group.GroupID]; ok {
|
||||
for index, permission := range p.Permissions[group.GroupID] {
|
||||
if permission == desiredPermission {
|
||||
indexList = append(indexList, index)
|
||||
} else if permission == "write" && desiredPermission == "read" {
|
||||
indexList = append(indexList, index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return indexList
|
||||
}
|
||||
314
authz/authorization_test.go
Normal file
314
authz/authorization_test.go
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
// 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 authz_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/molecula/featurebase/v2/authz"
|
||||
)
|
||||
|
||||
func TestAuth_ReadPermissionsFile(t *testing.T) {
|
||||
|
||||
singleInput := `user-groups:
|
||||
"dca35310-ecda-4f23-86cd-876aee55906b":
|
||||
"test": "read"
|
||||
admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"`
|
||||
|
||||
multiInput := `user-groups:
|
||||
"dca35310-ecda-4f23-86cd-876aee55906b":
|
||||
"test": "read"
|
||||
"test2": "write"
|
||||
"dca35310-ecda-4f23-86cd-876aee559900":
|
||||
"test": "write"
|
||||
admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"`
|
||||
|
||||
singlePermission := authz.GroupPermissions{
|
||||
Permissions: map[string]map[string]string{
|
||||
"dca35310-ecda-4f23-86cd-876aee55906b": {"test": "read"},
|
||||
},
|
||||
Admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe",
|
||||
}
|
||||
|
||||
multiPermission := authz.GroupPermissions{
|
||||
Permissions: map[string]map[string]string{
|
||||
"dca35310-ecda-4f23-86cd-876aee55906b": {"test": "read", "test2": "write"},
|
||||
"dca35310-ecda-4f23-86cd-876aee559900": {"test": "write"}},
|
||||
Admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
output authz.GroupPermissions
|
||||
}{
|
||||
{singleInput, singlePermission},
|
||||
{multiInput, multiPermission},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
||||
permFile := strings.NewReader(test.input)
|
||||
|
||||
var p authz.GroupPermissions
|
||||
err := p.ReadPermissionsFile(permFile)
|
||||
if err != nil {
|
||||
t.Fatalf("readPermissionsFile error: %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(p, test.output) {
|
||||
t.Fatalf("expected output %s, but got %s", test.output, p)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuth_GetPermissions(t *testing.T) {
|
||||
|
||||
// initializes different example of permissions file in yaml
|
||||
permissions1 := `"user-groups":
|
||||
"dca35310-ecda-4f23-86cd-876aee55906b":
|
||||
"test": "read"
|
||||
admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"`
|
||||
|
||||
permissions2 := `"user-groups":
|
||||
"dca35310-ecda-4f23-86cd-876aee559900":
|
||||
"test": "write"
|
||||
admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"`
|
||||
|
||||
permissions3 := `"user-groups":
|
||||
"dca35310-ecda-4f23-86cd-876aee55906b":
|
||||
"test": "write"
|
||||
"test2": "read"
|
||||
"dca35310-ecda-4f23-86cd-876aee559900":
|
||||
"test": "read"
|
||||
admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"`
|
||||
|
||||
permissions4 := `"user-groups":
|
||||
"dca35310-ecda-4f23-86cd-876aee559900":
|
||||
"test": ""
|
||||
admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"`
|
||||
|
||||
// initializes groups that are returned from identity provider
|
||||
groupName := "name"
|
||||
userId := "user-id"
|
||||
groupsList1 := []authz.Group{}
|
||||
groupsList2 := []authz.Group{{userId, "fake-group", groupName}}
|
||||
groupsList3 := []authz.Group{
|
||||
{userId, "dca35310-ecda-4f23-86cd-876aee55906b", groupName},
|
||||
{userId, "dca35310-ecda-4f23-86cd-876aee559900", groupName},
|
||||
}
|
||||
groupsList4 := []authz.Group{{userId, "ac97c9e2-346b-42a2-b6da-18bcb61a32fe", groupName}}
|
||||
|
||||
tests := []struct {
|
||||
yamlData string
|
||||
groups []authz.Group
|
||||
index string
|
||||
userAccess string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
permissions1,
|
||||
groupsList1,
|
||||
"test",
|
||||
"",
|
||||
"user is not part of any groups in identity provider",
|
||||
},
|
||||
{
|
||||
permissions1,
|
||||
groupsList3,
|
||||
"test1",
|
||||
"",
|
||||
"does not have permission to index",
|
||||
},
|
||||
{
|
||||
permissions2,
|
||||
groupsList2,
|
||||
"test",
|
||||
"",
|
||||
"does not have permission to FeatureBase",
|
||||
},
|
||||
{
|
||||
permissions1,
|
||||
groupsList3,
|
||||
"test",
|
||||
"read",
|
||||
"",
|
||||
},
|
||||
{
|
||||
permissions2,
|
||||
groupsList3,
|
||||
"test",
|
||||
"write",
|
||||
"",
|
||||
},
|
||||
{
|
||||
permissions3,
|
||||
groupsList4,
|
||||
"test",
|
||||
"admin",
|
||||
"",
|
||||
},
|
||||
{
|
||||
permissions4,
|
||||
groupsList3,
|
||||
"test",
|
||||
"",
|
||||
"no permissions found",
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
||||
|
||||
permFile := strings.NewReader(test.yamlData)
|
||||
|
||||
var p authz.GroupPermissions
|
||||
if err := p.ReadPermissionsFile(permFile); err != nil {
|
||||
t.Errorf("Error: %s", err)
|
||||
}
|
||||
|
||||
p1, err := p.GetPermissions(test.groups, test.index)
|
||||
|
||||
if p1 != test.userAccess {
|
||||
t.Errorf("expected permission to be %s, but got %s", test.userAccess, p1)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), test.err) {
|
||||
t.Errorf("expected error to contain %s, but got %s", test.err, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuth_IsAdmin(t *testing.T) {
|
||||
|
||||
group1 := []authz.Group{
|
||||
{"admin-user-id", "ac97c9e2-346b-42a2-b6da-18bcb61a32fe", "admin-group"},
|
||||
}
|
||||
|
||||
group2 := []authz.Group{
|
||||
{"user-id", "dca35310-ecda-4f23-86cd-876aee55906b", "group-name"},
|
||||
}
|
||||
|
||||
groupPermissions := authz.GroupPermissions{
|
||||
Permissions: map[string]map[string]string{
|
||||
"dca35310-ecda-4f23-86cd-876aee55906b": {"test": "write"},
|
||||
},
|
||||
Admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
groups []authz.Group
|
||||
groupPermissions authz.GroupPermissions
|
||||
output bool
|
||||
}{
|
||||
{
|
||||
group1, groupPermissions, true,
|
||||
},
|
||||
{
|
||||
group2, groupPermissions, false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
||||
p := test.groupPermissions
|
||||
resp := p.IsAdmin(test.groups)
|
||||
if resp != test.output {
|
||||
t.Errorf("expected %t, but got %t", test.output, resp)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuth_GetAuthorizedIndexList(t *testing.T) {
|
||||
|
||||
group1 := []authz.Group{
|
||||
{"user-id", "dca35310-ecda-4f23-86cd-876aee55906b", "group-name"},
|
||||
}
|
||||
|
||||
group2 := []authz.Group{
|
||||
{"admin-user-id", "ac97c9e2-346b-42a2-b6da-18bcb61a32fe", "admin-group"},
|
||||
}
|
||||
|
||||
group3 := []authz.Group{
|
||||
{"user-id", "dca35310-ecda-4f23-86cd-876aee559900", "group-name"},
|
||||
}
|
||||
|
||||
p := authz.GroupPermissions{
|
||||
Permissions: map[string]map[string]string{
|
||||
"dca35310-ecda-4f23-86cd-876aee55906b": {
|
||||
"test1": "read",
|
||||
"test2": "write",
|
||||
},
|
||||
"dca35310-ecda-4f23-86cd-876aee559900": {
|
||||
"test3": "read",
|
||||
},
|
||||
},
|
||||
Admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
groups []authz.Group
|
||||
permission string
|
||||
output []string
|
||||
}{
|
||||
{
|
||||
group1,
|
||||
"read",
|
||||
[]string{"test1", "test2"},
|
||||
},
|
||||
{
|
||||
group1,
|
||||
"write",
|
||||
[]string{"test2"},
|
||||
},
|
||||
{
|
||||
group3,
|
||||
"write",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
group2,
|
||||
"read",
|
||||
[]string{"test1", "test2", "test3"},
|
||||
},
|
||||
{
|
||||
group2,
|
||||
"write",
|
||||
[]string{"test1", "test2", "test3"},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
||||
|
||||
indexList := p.GetAuthorizedIndexList(test.groups, test.permission)
|
||||
sort.Strings(indexList)
|
||||
|
||||
if !reflect.DeepEqual(indexList, test.output) {
|
||||
t.Errorf("expected %s, but got %s", test.output, indexList)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -117,5 +117,5 @@ func BuildServerFlags(cmd *cobra.Command, srv *server.Command) {
|
|||
flags.StringVar(&srv.Config.Auth.TokenURL, "auth.token-url", srv.Config.Auth.TokenURL, "Identity Provider's Token URL.")
|
||||
flags.StringVar(&srv.Config.Auth.GroupEndpointURL, "auth.group-endpoint-url", srv.Config.Auth.GroupEndpointURL, "Identity Provider's Group endpoint URL.")
|
||||
flags.StringVar(&srv.Config.Auth.ScopeURL, "auth.scope-url", srv.Config.Auth.ScopeURL, "Identity Provider's Scope URL.")
|
||||
|
||||
flags.StringVar(&srv.Config.Auth.PermissionsFile, "auth.permissions", srv.Config.Auth.PermissionsFile, "Permissions' file with group authorization.")
|
||||
}
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -54,7 +54,7 @@ require (
|
|||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
google.golang.org/grpc v1.28.0
|
||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
modernc.org/mathutil v1.0.0
|
||||
modernc.org/strutil v1.0.0
|
||||
sigs.k8s.io/yaml v1.2.0 // indirect
|
||||
|
|
|
|||
|
|
@ -380,4 +380,5 @@ log-path = "/var/log/molecula/featurebase.log"
|
|||
# authorize-url = ""
|
||||
# token-url = ""
|
||||
# group-endpoint-url = ""
|
||||
# scope-url = ""
|
||||
# scope-url = ""
|
||||
# permissions = ""
|
||||
102
server/config.go
102
server/config.go
|
|
@ -4,15 +4,18 @@ package server
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/molecula/featurebase/v2/auth"
|
||||
"github.com/molecula/featurebase/v2/authz"
|
||||
petcd "github.com/molecula/featurebase/v2/etcd"
|
||||
rbfcfg "github.com/molecula/featurebase/v2/rbf/cfg"
|
||||
"github.com/molecula/featurebase/v2/storage"
|
||||
|
|
@ -231,7 +234,7 @@ type Config struct {
|
|||
SchemaDetailsOn bool `toml:"schema-details-on"`
|
||||
|
||||
// Enable AuthZ/AuthN
|
||||
Auth auth.Auth `toml:"auth"`
|
||||
Auth authz.Auth `toml:"auth"`
|
||||
}
|
||||
|
||||
// Namespace returns the namespace to use based on the Future flag.
|
||||
|
|
@ -596,9 +599,9 @@ func lookupAddr(ctx context.Context, resolver *net.Resolver, host string) (strin
|
|||
return addrs[0].String(), nil
|
||||
}
|
||||
|
||||
func (c *Config) ValidateAuth() ([]error, error) {
|
||||
func (c *Config) ValidateAuth() (errors []error) {
|
||||
if !c.Auth.Enable {
|
||||
return []error{}, nil
|
||||
return
|
||||
}
|
||||
authConfig := map[string]string{
|
||||
"ClientId": c.Auth.ClientId,
|
||||
|
|
@ -609,7 +612,6 @@ func (c *Config) ValidateAuth() ([]error, error) {
|
|||
"ScopeURL": c.Auth.ScopeURL,
|
||||
}
|
||||
|
||||
errors := make([]error, 0)
|
||||
for name, value := range authConfig {
|
||||
if value == "" {
|
||||
errors = append(errors, fmt.Errorf("empty string for auth config %s", name))
|
||||
|
|
@ -624,17 +626,95 @@ func (c *Config) ValidateAuth() ([]error, error) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if len(errors) > 0 {
|
||||
return errors, fmt.Errorf("there were errors validating config")
|
||||
return errors
|
||||
}
|
||||
|
||||
func (c *Config) ValidatePermissions(permsFile io.Reader) (errors []error) {
|
||||
|
||||
var p authz.GroupPermissions
|
||||
if err := p.ReadPermissionsFile(permsFile); err != nil {
|
||||
return append(errors, err)
|
||||
}
|
||||
return errors, nil
|
||||
|
||||
if len(p.Permissions) == 0 {
|
||||
return append(errors, fmt.Errorf("no group permissions found in permissions file: %s", c.Auth.PermissionsFile))
|
||||
}
|
||||
|
||||
for groupId, indexPerm := range p.Permissions {
|
||||
if groupId == "" {
|
||||
errors = append(errors, fmt.Errorf("empty string for group id in permissions file %s", c.Auth.PermissionsFile))
|
||||
continue
|
||||
}
|
||||
|
||||
for index, perm := range indexPerm {
|
||||
if index == "" {
|
||||
errors = append(errors, fmt.Errorf("empty string for index for group id %s in permissions file %s ", groupId, c.Auth.PermissionsFile))
|
||||
continue
|
||||
}
|
||||
|
||||
if perm == "" {
|
||||
errors = append(errors, fmt.Errorf("empty string for permission for group id %s and index %s in permissions file %s", groupId, index, c.Auth.PermissionsFile))
|
||||
continue
|
||||
}
|
||||
|
||||
if !((perm == "write") || (perm == "read")) {
|
||||
errors = append(errors, fmt.Errorf("not a valid permission %s for group id %s and index %s in permissions file %s; expected permissions are read or write", perm, groupId, index, c.Auth.PermissionsFile))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if p.Admin == "" {
|
||||
errors = append(errors, fmt.Errorf("empty string for admin in permissions file: %s", c.Auth.PermissionsFile))
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
func (c *Config) ValidatePermissionsFile() (err error) {
|
||||
|
||||
if c.Auth.PermissionsFile == "" {
|
||||
return fmt.Errorf("empty string for auth config permissions file")
|
||||
}
|
||||
|
||||
fileExt := filepath.Ext(c.Auth.PermissionsFile)
|
||||
if (fileExt != ".yaml") && (fileExt != ".yml") {
|
||||
return fmt.Errorf("invalid file extension for auth config permissions file: %s", c.Auth.PermissionsFile)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Config) MustValidateAuth() {
|
||||
if errors, err := c.ValidateAuth(); err != nil {
|
||||
for _, e := range errors {
|
||||
|
||||
errorsAuth := c.ValidateAuth()
|
||||
if len(errorsAuth) > 0 {
|
||||
for _, e := range errorsAuth {
|
||||
log.Println(e)
|
||||
}
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var errorsPerm []error
|
||||
errorsPermFile := c.ValidatePermissionsFile()
|
||||
if errorsPermFile == nil {
|
||||
permsFile, err := os.Open(c.Auth.PermissionsFile)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
defer permsFile.Close()
|
||||
|
||||
errorsPerm = c.ValidatePermissions(permsFile)
|
||||
if len(errorsPerm) > 0 {
|
||||
for _, e := range errorsPerm {
|
||||
log.Println(e)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
log.Println(errorsPermFile)
|
||||
}
|
||||
|
||||
if len(errorsAuth) > 0 || len(errorsPerm) > 0 || errorsPermFile != nil {
|
||||
log.Fatal(fmt.Errorf("there were errors validating authN/authZ config and/or permissions"))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/molecula/featurebase/v2/auth"
|
||||
"github.com/molecula/featurebase/v2/authz"
|
||||
)
|
||||
|
||||
type addrs struct{ bind, advertise string }
|
||||
|
|
@ -284,14 +284,14 @@ func TestConfig_validateAuth(t *testing.T) {
|
|||
validTestURL := "https://url.com/"
|
||||
validClientID := "clientid"
|
||||
validClientSecret := "clientSecret"
|
||||
notValidURL := "not-a-url"
|
||||
invalidURL := "not-a-url"
|
||||
emptyString := ""
|
||||
enable := true
|
||||
disable := false
|
||||
|
||||
tests := []struct {
|
||||
expErrs []string
|
||||
input auth.Auth
|
||||
input authz.Auth
|
||||
}{
|
||||
|
||||
{
|
||||
|
|
@ -304,7 +304,7 @@ func TestConfig_validateAuth(t *testing.T) {
|
|||
errorMesgEmpty,
|
||||
errorMesgEmpty,
|
||||
},
|
||||
auth.Auth{
|
||||
authz.Auth{
|
||||
Enable: enable,
|
||||
ClientId: emptyString,
|
||||
ClientSecret: emptyString,
|
||||
|
|
@ -314,130 +314,25 @@ func TestConfig_validateAuth(t *testing.T) {
|
|||
ScopeURL: emptyString,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Auth enabled, some configs are set to empty string
|
||||
[]string{
|
||||
errorMesgEmpty,
|
||||
errorMesgEmpty,
|
||||
errorMesgEmpty,
|
||||
errorMesgEmpty,
|
||||
errorMesgEmpty,
|
||||
},
|
||||
auth.Auth{
|
||||
Enable: enable,
|
||||
ClientId: validClientID,
|
||||
ClientSecret: emptyString,
|
||||
AuthorizeURL: emptyString,
|
||||
TokenURL: emptyString,
|
||||
GroupEndpointURL: emptyString,
|
||||
ScopeURL: emptyString,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Auth enabled, some configs are set to empty string
|
||||
[]string{
|
||||
errorMesgEmpty,
|
||||
errorMesgEmpty,
|
||||
errorMesgEmpty,
|
||||
errorMesgEmpty,
|
||||
errorMesgEmpty,
|
||||
},
|
||||
auth.Auth{
|
||||
Enable: enable,
|
||||
ClientId: emptyString,
|
||||
ClientSecret: validClientSecret,
|
||||
AuthorizeURL: emptyString,
|
||||
TokenURL: emptyString,
|
||||
GroupEndpointURL: emptyString,
|
||||
ScopeURL: emptyString,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Auth enabled, some configs are set to empty string
|
||||
[]string{
|
||||
errorMesgEmpty,
|
||||
errorMesgEmpty,
|
||||
errorMesgEmpty,
|
||||
errorMesgEmpty,
|
||||
},
|
||||
auth.Auth{
|
||||
Enable: enable,
|
||||
ClientId: validClientID,
|
||||
ClientSecret: validClientSecret,
|
||||
AuthorizeURL: emptyString,
|
||||
TokenURL: emptyString,
|
||||
GroupEndpointURL: emptyString,
|
||||
ScopeURL: emptyString,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Auth enabled, some configs are set to empty string
|
||||
[]string{
|
||||
errorMesgEmpty,
|
||||
errorMesgEmpty,
|
||||
errorMesgEmpty,
|
||||
},
|
||||
auth.Auth{
|
||||
Enable: enable,
|
||||
ClientId: validClientID,
|
||||
ClientSecret: validClientSecret,
|
||||
AuthorizeURL: validTestURL,
|
||||
TokenURL: emptyString,
|
||||
GroupEndpointURL: emptyString,
|
||||
ScopeURL: emptyString,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Auth enabled, some configs are set to empty string
|
||||
[]string{
|
||||
errorMesgEmpty,
|
||||
errorMesgEmpty,
|
||||
},
|
||||
auth.Auth{
|
||||
Enable: enable,
|
||||
ClientId: validClientID,
|
||||
ClientSecret: validClientSecret,
|
||||
AuthorizeURL: validTestURL,
|
||||
TokenURL: validTestURL,
|
||||
GroupEndpointURL: emptyString,
|
||||
ScopeURL: emptyString,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Auth enabled, some strings are set to invalid URL
|
||||
[]string{
|
||||
errorMesgURL,
|
||||
},
|
||||
auth.Auth{
|
||||
authz.Auth{
|
||||
Enable: enable,
|
||||
ClientId: validClientID,
|
||||
ClientSecret: validClientSecret,
|
||||
AuthorizeURL: notValidURL,
|
||||
AuthorizeURL: invalidURL,
|
||||
TokenURL: validTestURL,
|
||||
GroupEndpointURL: validTestURL,
|
||||
ScopeURL: validTestURL,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Auth enabled, some strings are set to invalid URL
|
||||
[]string{
|
||||
errorMesgURL,
|
||||
errorMesgURL,
|
||||
},
|
||||
auth.Auth{
|
||||
Enable: enable,
|
||||
ClientId: validClientID,
|
||||
ClientSecret: validClientSecret,
|
||||
AuthorizeURL: validTestURL,
|
||||
TokenURL: notValidURL,
|
||||
GroupEndpointURL: notValidURL,
|
||||
ScopeURL: validTestURL,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Auth enabled, all configs are set properly
|
||||
[]string{},
|
||||
auth.Auth{
|
||||
authz.Auth{
|
||||
Enable: enable,
|
||||
ClientId: validClientID,
|
||||
ClientSecret: validClientSecret,
|
||||
|
|
@ -450,7 +345,7 @@ func TestConfig_validateAuth(t *testing.T) {
|
|||
{
|
||||
// Auth disabled, all configs are set to empty string
|
||||
[]string{},
|
||||
auth.Auth{
|
||||
authz.Auth{
|
||||
Enable: disable,
|
||||
ClientId: emptyString,
|
||||
ClientSecret: emptyString,
|
||||
|
|
@ -467,9 +362,9 @@ func TestConfig_validateAuth(t *testing.T) {
|
|||
c := NewConfig()
|
||||
c.Auth = test.input
|
||||
|
||||
errors, err := c.ValidateAuth()
|
||||
errors := c.ValidateAuth()
|
||||
if len(test.expErrs) > 0 {
|
||||
if err == nil {
|
||||
if errors == nil {
|
||||
t.Fatal("expected errors, but none were found")
|
||||
}
|
||||
}
|
||||
|
|
@ -487,3 +382,113 @@ func TestConfig_validateAuth(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfig_validatePermissions(t *testing.T) {
|
||||
permissions0 := ``
|
||||
|
||||
permissions1 := `user-groups:
|
||||
"":
|
||||
"test": "read"
|
||||
admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"`
|
||||
|
||||
permissions2 := `user-groups:
|
||||
"dca35310-ecda-4f23-86cd-876aee559900":
|
||||
"": "write"
|
||||
admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"`
|
||||
|
||||
permissions3 := `user-groups:
|
||||
"dca35310-ecda-4f23-86cd-876aee559900":
|
||||
"test": ""
|
||||
admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"`
|
||||
|
||||
permissions4 := `user-groups:
|
||||
"dca35310-ecda-4f23-86cd-876aee559900":
|
||||
"test": "readwrite"
|
||||
admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"`
|
||||
|
||||
permissions5 := `user-groups:
|
||||
"dca35310-ecda-4f23-86cd-876aee559900":
|
||||
"test": "read"`
|
||||
|
||||
tests := []struct {
|
||||
err string
|
||||
input string
|
||||
}{
|
||||
{
|
||||
"no group permissions found in permissions file",
|
||||
permissions0,
|
||||
},
|
||||
{
|
||||
"empty string for group id",
|
||||
permissions1,
|
||||
},
|
||||
{
|
||||
"empty string for index",
|
||||
permissions2,
|
||||
},
|
||||
{
|
||||
"empty string for permission",
|
||||
permissions3,
|
||||
},
|
||||
{
|
||||
"not a valid permission",
|
||||
permissions4,
|
||||
},
|
||||
{
|
||||
"empty string for admin in permissions file",
|
||||
permissions5,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
||||
|
||||
c := NewConfig()
|
||||
c.Auth.PermissionsFile = "test.yaml"
|
||||
|
||||
permFile := strings.NewReader(test.input)
|
||||
errors := c.ValidatePermissions(permFile)
|
||||
|
||||
if errors == nil {
|
||||
t.Fatal("expected errors, but none were found")
|
||||
}
|
||||
|
||||
for _, err := range errors {
|
||||
if !strings.Contains(err.Error(), test.err) {
|
||||
t.Errorf("expected error to contain %s, but got %s", test.err, err.Error())
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfig_validatePermissionsFilename(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
err string
|
||||
input string
|
||||
}{
|
||||
{
|
||||
"empty string for auth config permissions file",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"invalid file extension for auth config permissions file",
|
||||
"permissions.txt",
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
||||
c := NewConfig()
|
||||
c.Auth.PermissionsFile = test.input
|
||||
|
||||
if err := c.ValidatePermissionsFile(); err != nil {
|
||||
if !strings.Contains(err.Error(), test.err) {
|
||||
t.Errorf("expected error to contain %s, but got %s", test.err, err.Error())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import (
|
|||
"golang.org/x/sync/errgroup"
|
||||
|
||||
pilosa "github.com/molecula/featurebase/v2"
|
||||
"github.com/molecula/featurebase/v2/authz"
|
||||
"github.com/molecula/featurebase/v2/boltdb"
|
||||
"github.com/molecula/featurebase/v2/encoding/proto"
|
||||
petcd "github.com/molecula/featurebase/v2/etcd"
|
||||
|
|
@ -224,6 +225,18 @@ func (m *Command) Start() (err error) {
|
|||
|
||||
if m.Config.Auth.Enable {
|
||||
m.Config.MustValidateAuth()
|
||||
|
||||
permsFile, err := os.Open(m.Config.Auth.PermissionsFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer permsFile.Close()
|
||||
|
||||
var p authz.GroupPermissions
|
||||
if err = p.ReadPermissionsFile(permsFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize server.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue