Skip to content

Commit

Permalink
satellite/admin/back-office: Implement authorization
Browse files Browse the repository at this point in the history
Implement the authorization that will hook into each endpoint handler
through a wrapping handler for defining the permissions that each
endpoint requires.

Change-Id: I9c8f12b58f48e849e7ea35f372dddce5c9cfc5b5
  • Loading branch information
ifraixedes authored and Storj Robot committed Nov 16, 2023
1 parent 720b75a commit 418673f
Show file tree
Hide file tree
Showing 5 changed files with 459 additions and 6 deletions.
169 changes: 169 additions & 0 deletions satellite/admin/back-office/authorization.go
@@ -0,0 +1,169 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.

package admin

import (
"net/http"
"strings"

"github.com/zeebo/errs"
"go.uber.org/zap"

"storj.io/storj/private/api"
)

// These constants are the list of permissions that the service uses for authorizing users to
// perform operations.
const (
PermAccountView Permission = 1 << iota
PermAccountChangeEmail
PermAccountDisableMFA
PermAccountChangeLimits
PermAccountSetDataPlacement
PermAccountRemoveDataPlacement
PermAccountSetUserAgent
PermAccountSuspendTemporary
PermAccountReActivateTemporary
PermAccountSuspendPermanently
PermAccountReActivatePermanently
PermAccountDeleteNoData
PermAccountDeleteWithData
PermProjectView
PermProjectSetLimits
PermProjectSetDataPlacement
PermProjectRemoveDataPlacement
PermProjectSetUserAgent
PermProjectSendInvitation
PermBucketView
PermBucketSetDataPlacement
PermBucketRemoveDataPlacement
PermBucketSetUserAgent
)

// These constants are the list of roles that users can have and the service uses to match
// permissions to perform operations.
const (
RoleAdmin = Authorization(
PermAccountView | PermAccountChangeEmail | PermAccountDisableMFA | PermAccountChangeLimits |
PermAccountSetDataPlacement | PermAccountRemoveDataPlacement | PermAccountSetUserAgent |
PermAccountSuspendTemporary | PermAccountReActivateTemporary | PermAccountSuspendPermanently |
PermAccountReActivatePermanently | PermAccountDeleteNoData | PermAccountDeleteWithData |
PermProjectView | PermProjectSetLimits | PermProjectSetDataPlacement |
PermProjectRemoveDataPlacement | PermProjectSetUserAgent | PermProjectSendInvitation |
PermBucketView | PermBucketSetDataPlacement | PermBucketRemoveDataPlacement |
PermBucketSetUserAgent,
)
RoleViewer = Authorization(PermAccountView | PermProjectView | PermBucketView)
RoleCustomerSupport = Authorization(
PermAccountView | PermAccountChangeEmail | PermAccountDisableMFA | PermAccountChangeLimits |
PermAccountSetDataPlacement | PermAccountRemoveDataPlacement | PermAccountSetUserAgent |
PermAccountSuspendTemporary | PermAccountReActivateTemporary | PermAccountDeleteNoData |
PermProjectView | PermProjectSetLimits | PermProjectSetDataPlacement |
PermProjectRemoveDataPlacement | PermProjectSetUserAgent | PermProjectSendInvitation |
PermBucketView | PermBucketSetDataPlacement | PermBucketRemoveDataPlacement |
PermBucketSetUserAgent,
)
RoleFinanceManager = Authorization(
PermAccountView | PermAccountSuspendTemporary | PermAccountReActivateTemporary |
PermAccountSuspendPermanently | PermAccountReActivatePermanently | PermAccountDeleteNoData |
PermAccountDeleteWithData | PermProjectView | PermBucketView,
)
)

// ErrAuthorizer is the error class that wraps all the errors returned by the authorization.
var ErrAuthorizer = errs.Class("authorizer")

// Permission represents a permissions to perform an operation.
type Permission uint64

// Authorization specifies the permissions that user role has and validates if it has certain
// permissions.
type Authorization uint64

// Has returns true if auth has all the passed permissions.
func (auth Authorization) Has(perms ...Permission) bool {
for _, p := range perms {
if uint64(auth)&uint64(p) == 0 {
return false
}
}

return true
}

// Authorizer checks if a group has certain permissions.
type Authorizer struct {
log *zap.Logger
groupsRoles map[string]Authorization
}

// NewAuthorizer creates an Authorizer with the list of groups that are assigned to each different
// role. log is the parent logger where it will attach a prefix to identify messages coming from it.
//
// In the case that a group is assigned to more than one role, it will get the less permissive role.
func NewAuthorizer(
log *zap.Logger,
adminGroups, viewerGroups, customerSupportGroups, financeManagerGroups []string,
) *Authorizer {
groupsRoles := make(map[string]Authorization)

// NOTE the order of iterating over all the groups matters because in the case that a group is in
// more than one designed role, the group will get the role with less permissions that allow to
// perform devastating operations.

for _, g := range adminGroups {
groupsRoles[g] = RoleAdmin
}

for _, g := range financeManagerGroups {
groupsRoles[g] = RoleFinanceManager
}

for _, g := range customerSupportGroups {
groupsRoles[g] = RoleCustomerSupport
}

for _, g := range viewerGroups {
groupsRoles[g] = RoleViewer
}

return &Authorizer{
log: log.Named("authorizer"),
groupsRoles: groupsRoles,
}
}

// HasPermissions check if group has all perms.
func (auth *Authorizer) HasPermissions(group string, perms ...Permission) bool {
groupAuth, ok := auth.groupsRoles[group]
if !ok {
return false
}

return groupAuth.Has(perms...)
}

// Middleware returns an HTTP handler which verifies if the request is performed by a user with a
// role that allows all the passed permissions.
func (auth *Authorizer) Middleware(next http.Handler, perms ...Permission) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
groupsh := r.Header.Get("X-Forwarded-Groups")
if groupsh == "" {
err := Error.Wrap(ErrAuthorizer.New("You do not belong to any group"))
api.ServeError(auth.log, w, http.StatusUnauthorized, err)
return
}

groups := strings.Split(groupsh, ",")
for _, g := range groups {
if auth.HasPermissions(g, perms...) {
next.ServeHTTP(w, r)
return
}
}

err := Error.Wrap(ErrAuthorizer.New("Not enough permissions (your groups: %s)", groupsh))
api.ServeError(auth.log, w, http.StatusUnauthorized, err)
})
}

0 comments on commit 418673f

Please sign in to comment.