Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

96 org members #100

Merged
merged 8 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ services:
- DB_DSN=postgresql://sensorbucket:sensorbucket@db:5432/tenants?sslmode=disable
- STATIC_PATH=services/tenants/static
- HTTP_WEBUI_BASE=/tenants
- KRATOS_ADMIN_API=http://kratos:4434
- SB_API=http://caddy

userworkers:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module sensorbucket.nl/sensorbucket
go 1.21

require (
github.com/Masterminds/squirrel v1.5.2
github.com/Masterminds/squirrel v1.5.4
github.com/aquilax/go-perlin v1.1.0
github.com/docker/docker v24.0.5+incompatible
github.com/fission/fission v1.19.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/squirrel v1.5.2 h1:UiOEi2ZX4RCSkpiNDQN5kro/XIBpSRk9iTqdIRPzUXE=
github.com/Masterminds/squirrel v1.5.2/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/Microsoft/hcsshim v0.10.0-rc.5 h1:JfkknPHBtfdC2Ezd+jpl8Kicw7UyhvUSzoy6xsqirwY=
Expand Down
15 changes: 3 additions & 12 deletions internal/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,10 @@ func DecodeJSON(r *http.Request, v interface{}) error {
}

err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
fmt.Fprintf(os.Stderr, "Error decoding JSON: %s\n", err)
return InvalidJSONError
var apiError *APIError
if errors.As(err, &apiError) {
return apiError
}
return nil
}

func DecodeJSONResponse(r *http.Response, v interface{}) error {
if r.Header.Get("content-type") != "application/json" {
return ContentTypeError
}

err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
fmt.Fprintf(os.Stderr, "Error decoding JSON: %s\n", err)
return InvalidJSONError
Expand Down
103 changes: 48 additions & 55 deletions pkg/auth/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,25 @@ package auth

import (
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"strings"

"github.com/samber/lo"
)

var ErrPermissionInvalid = errors.New("permission value is invalid")

type PermissionSet interface {
Permissions() []Permission
}
"sensorbucket.nl/sensorbucket/internal/web"
)

type Permission string
var ErrPermissionInvalid = web.NewError(http.StatusBadRequest, "permission value is invalid", "ERR_PERMISSION_INVALID")

type Permissions []PermissionSet
type (
// Singular permission, already validated
Permission string
Permissions []Permission
)

// Permissions
const (
// Device permissions
READ_DEVICES Permission = "READ_DEVICES"
Expand Down Expand Up @@ -58,72 +60,63 @@ var allPermissions = Permissions{
WRITE_USER_WORKERS,
}

func (p Permission) Permissions() []Permission {
return []Permission{p}
func AllPermissions() Permissions {
return allPermissions
}

func (gotten Permissions) Fulfills(required Permissions) error {
flatGotten := gotten.Permissions()
flatRequired := required.Permissions()
missing, _ := lo.Difference(flatRequired, flatGotten)
var stringPermissionMap = lo.SliceToMap(allPermissions, func(item Permission) (string, Permission) {
return string(item), item
})

func (this Permissions) Fulfills(that Permissions) error {
_, missing := lo.Difference(this, that)
if len(missing) > 0 {
return fmt.Errorf("missing: %v", missing)
}
return nil
}

func (p Permissions) Permissions() []Permission {
return lo.Uniq(lo.Flatten(lo.Map(p, func(item PermissionSet, index int) []Permission { return item.Permissions() })))
}

func (p Permissions) Includes(other Permission) bool {
return lo.IndexOf(p.Permissions(), other) != -1
func stringToPermission(str string) (Permission, bool) {
p, ok := stringPermissionMap[str]
if !ok {
log.Printf("Tried converting non-existant string to permission: %s\n", str)
}
return p, ok
}

func (p Permissions) Validate() error {
invalidPermissions := lo.FilterMap(p.Permissions(), func(item Permission, _ int) (string, bool) {
if err := item.Valid(); err != nil {
return item.String(), true
func stringsToPermissions(keys []string) (Permissions, error) {
permissions := make([]Permission, 0, len(keys))
for _, str := range keys {
permission, ok := stringToPermission(str)
if !ok {
return nil, fmt.Errorf("%w: %s", ErrPermissionInvalid, str)
}
return "", false
})
if len(invalidPermissions) > 0 {
return fmt.Errorf("%w: %s", ErrPermissionInvalid, strings.Join(invalidPermissions, ", "))
permissions = append(permissions, permission)
}
return nil
return permissions, nil
}

func (s Permissions) String() string {
return strings.Join(
lo.Map(s.Permissions(), func(item Permission, _ int) string { return string(item) }),
", ",
)
func (permission Permission) String() string {
return string(permission)
}

func (s *Permissions) UnmarshalJSON(data []byte) error {
var permissionSlice []Permission
if err := json.Unmarshal(data, &permissionSlice); err != nil {
return err
func (permissions *Permissions) UnmarshalJSON(data []byte) error {
strings := []string{}
if err := json.Unmarshal(data, &strings); err != nil {
return fmt.Errorf("could not unmarshal permissions: %w", err)
}
permissions := Permissions{}
for _, p := range permissionSlice {
permissions = append(permissions, p)
perms, err := stringsToPermissions(strings)
if err != nil {
return fmt.Errorf("could not unmarshal permissions: %w", err)
}
*s = permissions
*permissions = perms
return nil
}

func (p Permission) String() string {
return string(p)
}

func (p Permission) Valid() error {
if allPermissions.Includes(p) {
return nil
func (permissions Permissions) Validate() error {
_, invalidPermissions := lo.Difference(allPermissions, permissions)
if len(invalidPermissions) > 0 {
return fmt.Errorf("%w: %s", ErrPermissionInvalid, strings.Join(lo.Map(invalidPermissions, func(item Permission, index int) string { return string(item) }), ", "))
}
return fmt.Errorf("%w (value: %s)", ErrPermissionInvalid, p)
}

func AllPermissions() PermissionSet {
return allPermissions
return nil
}
76 changes: 16 additions & 60 deletions pkg/auth/permissions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,75 +7,31 @@ import (
"github.com/stretchr/testify/require"
)

func TestPermissionsValid(t *testing.T) {
func TestStringToPermissions(t *testing.T) {
testCases := []struct {
desc string
permission Permission
expectedError error
}{
{
desc: "valid permission",
permission: WRITE_DEVICES,
expectedError: nil,
},
{
desc: "invalid permission",
permission: Permission("NON_EXISTANT"),
expectedError: ErrPermissionInvalid,
},
}
for _, tC := range testCases {
t.Run(tC.desc, func(t *testing.T) {
assert.ErrorIs(t, tC.permission.Valid(), tC.expectedError)
})
}
}

func TestPermissionsFlattenCorrectly(t *testing.T) {
testCases := []struct {
desc string
set Permissions
expected []Permission
desc string
strings []string
expectedError error
expectedPermissions Permissions
}{
{
desc: "Empty to empty",
set: Permissions{},
expected: []Permission{},
},
{
desc: "One to one",
set: Permissions{READ_DEVICES},
expected: []Permission{READ_DEVICES},
},
{
desc: "Two to Two",
set: Permissions{READ_DEVICES, WRITE_DEVICES},
expected: []Permission{READ_DEVICES, WRITE_DEVICES},
},
{
desc: "Group with one to one",
set: Permissions{Permissions{READ_DEVICES}},
expected: []Permission{READ_DEVICES},
},
{
desc: "Group with many to many",
set: Permissions{Permissions{READ_DEVICES, WRITE_DEVICES}},
expected: []Permission{READ_DEVICES, WRITE_DEVICES},
},
{
desc: "Group with groups",
set: Permissions{Permissions{Permissions{READ_DEVICES}, Permissions{WRITE_DEVICES}}},
expected: []Permission{READ_DEVICES, WRITE_DEVICES},
desc: "valid permission",
strings: []string{string(WRITE_DEVICES)},
expectedError: nil,
expectedPermissions: Permissions{WRITE_DEVICES},
},
{
desc: "Remove duplicates",
set: Permissions{READ_DEVICES, READ_DEVICES},
expected: []Permission{READ_DEVICES},
desc: "invalid permission",
strings: []string{"INVALID_PERM"},
expectedError: ErrPermissionInvalid,
expectedPermissions: nil,
},
}
for _, tC := range testCases {
t.Run(tC.desc, func(t *testing.T) {
assert.Equal(t, tC.expected, tC.set.Permissions())
perms, err := stringsToPermissions(tC.strings)
assert.ErrorIs(t, err, tC.expectedError)
assert.Equal(t, tC.expectedPermissions, perms)
})
}
}
Expand Down
4 changes: 2 additions & 2 deletions services/tenants/apikeys/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (s *Service) GenerateNewApiKey(name string, tenantId int64, permissions aut
if err := permissions.Validate(); err != nil {
return "", fmt.Errorf("%w: %w", ErrPermissionsInvalid, err)
}
tenant, err := s.tenantStore.GetTenantById(tenantId)
tenant, err := s.tenantStore.GetTenantByID(tenantId)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -166,5 +166,5 @@ type ApiKeyStore interface {
}

type TenantStore interface {
GetTenantById(id int64) (tenants.Tenant, error)
GetTenantByID(id int64) (*tenants.Tenant, error)
}
Loading
Loading