You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by de...@apache.org on 2018/05/24 15:31:25 UTC
[incubator-trafficcontrol] 03/08: Implemented tenancy checks for
the Origin API
This is an automated email from the ASF dual-hosted git repository.
dewrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-trafficcontrol.git
commit 3f5ecaf2f192a2bddbc0d853f2217291e65b913c
Author: Rawlin Peters <ra...@comcast.com>
AuthorDate: Fri Apr 27 14:18:30 2018 -0600
Implemented tenancy checks for the Origin API
---
lib/go-tc/alerts.go | 2 +
lib/go-tc/constants.go | 2 +
traffic_ops/traffic_ops_golang/origin/origins.go | 99 +++++++++++++++++++++-
.../traffic_ops_golang/origin/origins_test.go | 3 +
traffic_ops/traffic_ops_golang/tenant/tenancy.go | 12 +++
5 files changed, 116 insertions(+), 2 deletions(-)
diff --git a/lib/go-tc/alerts.go b/lib/go-tc/alerts.go
index 4ce344b..cd6349e 100644
--- a/lib/go-tc/alerts.go
+++ b/lib/go-tc/alerts.go
@@ -83,6 +83,8 @@ func HandleErrorsWithType(errs []error, errType ApiErrorType, handleErrs func(st
handleErrs(http.StatusBadRequest, errs...)
case DataMissingError:
handleErrs(http.StatusNotFound, errs...)
+ case ForbiddenError:
+ handleErrs(http.StatusForbidden, errs...)
default:
log.Errorf("received unknown ApiErrorType from read: %s\n", errType.String())
handleErrs(http.StatusInternalServerError, errs...)
diff --git a/lib/go-tc/constants.go b/lib/go-tc/constants.go
index e5e20d9..35ae649 100644
--- a/lib/go-tc/constants.go
+++ b/lib/go-tc/constants.go
@@ -24,6 +24,8 @@ type ErrorConstant string
func (e ErrorConstant) Error() string { return string(e) }
const DBError = ErrorConstant("database access error")
+const NilTenantError = ErrorConstant("tenancy is enabled but request tenantID is nil")
+const TenantUserNotAuthError = ErrorConstant("user not authorized for requested tenant")
const ApplicationJson = "application/json"
const Gzip = "gzip"
diff --git a/traffic_ops/traffic_ops_golang/origin/origins.go b/traffic_ops/traffic_ops_golang/origin/origins.go
index d589913..e16b021 100644
--- a/traffic_ops/traffic_ops_golang/origin/origins.go
+++ b/traffic_ops/traffic_ops_golang/origin/origins.go
@@ -20,6 +20,7 @@ package origin
*/
import (
+ "database/sql"
"errors"
"fmt"
"strconv"
@@ -30,6 +31,7 @@ import (
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/auth"
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
+ "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/tovalidate"
validation "github.com/go-ozzo/ozzo-validation"
@@ -105,6 +107,62 @@ func (origin *TOOrigin) Validate(db *sqlx.DB) []error {
return tovalidate.ToErrors(validateErrs)
}
+// GetTenantID returns a pointer to the Origin's tenant ID from the DB and any error encountered
+func (origin *TOOrigin) GetTenantID(db *sqlx.DB) (*int, error) {
+ if origin.ID != nil {
+ var tenantID *int
+ if err := db.QueryRow(`SELECT tenant FROM origin where id = $1`, *origin.ID).Scan(&tenantID); err != nil {
+ if err == sql.ErrNoRows {
+ return nil, nil
+ }
+ return nil, fmt.Errorf("querying tenant ID for origin ID '%v': %v", *origin.ID, err)
+ }
+ return tenantID, nil
+ }
+ return nil, nil
+}
+
+func (origin *TOOrigin) IsTenantAuthorized(user auth.CurrentUser, db *sqlx.DB) (bool, error) {
+ currentTenantID, err := origin.GetTenantID(db)
+ if err != nil {
+ return false, err
+ }
+
+ if currentTenantID != nil && tenant.IsTenancyEnabled(db) {
+ return tenant.IsResourceAuthorizedToUser(*currentTenantID, user, db)
+ }
+
+ return true, nil
+}
+
+// filterAuthorized will filter a slice of OriginNullables based upon tenant. It assumes that tenancy is enabled
+func filterAuthorized(origins []v13.OriginNullable, user auth.CurrentUser, db *sqlx.DB) ([]v13.OriginNullable, error) {
+ newOrigins := []v13.OriginNullable{}
+ for _, origin := range origins {
+ if origin.TenantID == nil {
+ if origin.ID == nil {
+ return nil, errors.New("isResourceAuthorized for origin with nil ID: no tenant ID")
+ } else {
+ return nil, fmt.Errorf("isResourceAuthorized for origin %d: no tenant ID", *origin.ID)
+ }
+ }
+ // TODO add/use a helper func to make a single SQL call, for performance
+ ok, err := tenant.IsResourceAuthorizedToUser(*origin.TenantID, user, db)
+ if err != nil {
+ if origin.ID == nil {
+ return nil, errors.New("isResourceAuthorized for origin with nil ID: " + err.Error())
+ } else {
+ return nil, fmt.Errorf("isResourceAuthorized for origin %d: "+err.Error(), *origin.ID)
+ }
+ }
+ if !ok {
+ continue
+ }
+ newOrigins = append(newOrigins, origin)
+ }
+ return newOrigins, nil
+}
+
func (origin *TOOrigin) Read(db *sqlx.DB, params map[string]string, user auth.CurrentUser) ([]interface{}, []error, tc.ApiErrorType) {
returnable := []interface{}{}
@@ -115,6 +173,15 @@ func (origin *TOOrigin) Read(db *sqlx.DB, params map[string]string, user auth.Cu
return nil, errs, errType
}
+ var err error
+ if tenant.IsTenancyEnabled(db) {
+ origins, err = filterAuthorized(origins, user, db)
+ if err != nil {
+ log.Errorln("Checking tenancy: " + err.Error())
+ return nil, []error{errors.New("Error checking tenancy.")}, tc.SystemError
+ }
+ }
+
for _, origin := range origins {
returnable = append(returnable, origin)
}
@@ -197,12 +264,34 @@ LEFT JOIN tenant t ON o.tenant = t.id`
return selectStmt
}
+func checkTenancy(tenantID *int, db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) {
+ if tenant.IsTenancyEnabled(db) {
+ if tenantID == nil {
+ return tc.NilTenantError, tc.ForbiddenError
+ }
+ authorized, err := tenant.IsResourceAuthorizedToUser(*tenantID, user, db)
+ if err != nil {
+ return tc.DBError, tc.SystemError
+ }
+ if !authorized {
+ return tc.TenantUserNotAuthError, tc.ForbiddenError
+ }
+ }
+ return nil, tc.NoError
+}
+
//The TOOrigin implementation of the Updater interface
//all implementations of Updater should use transactions and return the proper errorType
//ParsePQUniqueConstraintError is used to determine if an origin with conflicting values exists
//if so, it will return an errorType of DataConflict and the type should be appended to the
//generic error message returned
func (origin *TOOrigin) Update(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) {
+ // TODO: enhance tenancy framework to handle this in isTenantAuthorized()
+ err, errType := checkTenancy(origin.TenantID, db, user)
+ if err != nil {
+ return err, errType
+ }
+
rollbackTransaction := true
tx, err := db.Beginx()
defer func() {
@@ -247,11 +336,11 @@ func (origin *TOOrigin) Update(db *sqlx.DB, user auth.CurrentUser) (error, tc.Ap
}
if rowsAffected == 0 {
- err = errors.New("no origin was inserted, no id was returned")
+ err = errors.New("no origin was updated, no id was returned")
log.Errorln(err)
return tc.DBError, tc.SystemError
} else if rowsAffected > 1 {
- err = errors.New("too many ids returned from origin insert")
+ err = errors.New("too many ids returned from origin update")
log.Errorln(err)
return tc.DBError, tc.SystemError
}
@@ -291,6 +380,12 @@ WHERE id=:id RETURNING last_updated`
//The insert sql returns the id and lastUpdated values of the newly inserted origin and have
//to be added to the struct
func (origin *TOOrigin) Create(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) {
+ // TODO: enhance tenancy framework to handle this in isTenantAuthorized()
+ err, errType := checkTenancy(origin.TenantID, db, user)
+ if err != nil {
+ return err, errType
+ }
+
rollbackTransaction := true
tx, err := db.Beginx()
defer func() {
diff --git a/traffic_ops/traffic_ops_golang/origin/origins_test.go b/traffic_ops/traffic_ops_golang/origin/origins_test.go
index 2d6b1de..de87304 100644
--- a/traffic_ops/traffic_ops_golang/origin/origins_test.go
+++ b/traffic_ops/traffic_ops_golang/origin/origins_test.go
@@ -159,6 +159,9 @@ func TestInterfaces(t *testing.T) {
if _, ok := i.(api.Identifier); !ok {
t.Errorf("origin must be Identifier")
}
+ if _, ok := i.(api.Tenantable); !ok {
+ t.Errorf("origin must be tenantable")
+ }
}
func TestValidate(t *testing.T) {
diff --git a/traffic_ops/traffic_ops_golang/tenant/tenancy.go b/traffic_ops/traffic_ops_golang/tenant/tenancy.go
index 1765240..a649e6b 100644
--- a/traffic_ops/traffic_ops_golang/tenant/tenancy.go
+++ b/traffic_ops/traffic_ops_golang/tenant/tenancy.go
@@ -118,6 +118,18 @@ func GetUserTenantList(user auth.CurrentUser, db *sqlx.DB) ([]Tenant, error) {
return tenants, nil
}
+// IsTenancyEnabled returns true if tenancy is enabled or false otherwise
+func IsTenancyEnabled(db *sqlx.DB) bool {
+ query := `SELECT COALESCE(value::boolean,FALSE) AS value FROM parameter WHERE name = 'use_tenancy' AND config_file = 'global' UNION ALL SELECT FALSE FETCH FIRST 1 ROW ONLY`
+ var useTenancy bool
+ err := db.QueryRow(query).Scan(&useTenancy)
+ if err != nil {
+ log.Errorf("Error checking if tenancy is enabled: %v", err)
+ return false
+ }
+ return useTenancy
+}
+
// returns a boolean value describing if the user has access to the provided resource tenant id and an error
// if use_tenancy is set to false (0 in the db) this method will return true allowing access.
func IsResourceAuthorizedToUser(resourceTenantID int, user auth.CurrentUser, db *sqlx.DB) (bool, error) {
--
To stop receiving notification emails like this one, please contact
dewrich@apache.org.