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.