You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by mi...@apache.org on 2018/05/17 02:25:54 UTC

[incubator-trafficcontrol] 08/17: implement role capabilities association on the roles crud endpoints

This is an automated email from the ASF dual-hosted git repository.

mitchell852 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-trafficcontrol.git

commit 1385d29a1dd3282e3002f1fe0552454e3b1ab35d
Author: Dylan Volz <Dy...@comcast.com>
AuthorDate: Tue May 1 12:18:12 2018 -0600

    implement role capabilities association on the roles crud endpoints
---
 lib/go-tc/v13/roles.go                             |  11 ++
 traffic_ops/traffic_ops_golang/auth/authorize.go   |  16 +--
 .../crconfig/deliveryservice_test.go               |   2 +-
 traffic_ops/traffic_ops_golang/role/roles.go       | 120 +++++++++++++++++++--
 traffic_ops/traffic_ops_golang/role/roles_test.go  |   3 +-
 traffic_ops/traffic_ops_golang/routing.go          |   2 +-
 6 files changed, 137 insertions(+), 17 deletions(-)

diff --git a/lib/go-tc/v13/roles.go b/lib/go-tc/v13/roles.go
index 3ecea8c..df343e1 100644
--- a/lib/go-tc/v13/roles.go
+++ b/lib/go-tc/v13/roles.go
@@ -1,3 +1,4 @@
+
 package v13
 
 /*
@@ -56,6 +57,11 @@ type Role struct {
 	//
 	// required: true
 	PrivLevel int `json:"privLevel" db:"priv_level"`
+
+	// Capabilities associated with the Role
+	//
+	// required: true
+	Capabilities []string `json:"capabilities" db:"capabilities"`
 }
 
 // RoleNullable ...
@@ -79,4 +85,9 @@ type RoleNullable struct {
 	//
 	// required: true
 	PrivLevel *int `json:"privLevel" db:"priv_level"`
+
+	// Capabilities associated with the Role
+	//
+	// required: true
+	Capabilities *[]string `json:"capabilities" db:"capabilities"`
 }
\ No newline at end of file
diff --git a/traffic_ops/traffic_ops_golang/auth/authorize.go b/traffic_ops/traffic_ops_golang/auth/authorize.go
index 194be38..2e5cff7 100644
--- a/traffic_ops/traffic_ops_golang/auth/authorize.go
+++ b/traffic_ops/traffic_ops_golang/auth/authorize.go
@@ -27,13 +27,15 @@ import (
 
 	"github.com/apache/incubator-trafficcontrol/lib/go-log"
 	"github.com/jmoiron/sqlx"
+	"github.com/lib/pq"
 )
 
 type CurrentUser struct {
-	UserName  string `json:"userName" db:"username"`
-	ID        int    `json:"id" db:"id"`
-	PrivLevel int    `json:"privLevel" db:"priv_level"`
-	TenantID  int    `json:"tenantId" db:"tenant_id"`
+	UserName     string         `json:"userName" db:"username"`
+	ID           int            `json:"id" db:"id"`
+	PrivLevel    int            `json:"privLevel" db:"priv_level"`
+	TenantID     int            `json:"tenantId" db:"tenant_id"`
+	Capabilities pq.StringArray `json:"capabilities" db:"capabilities"`
 }
 
 // PrivLevelInvalid - The Default Priv level
@@ -67,10 +69,10 @@ func GetCurrentUserFromDB(CurrentUserStmt *sqlx.Stmt, user string) CurrentUser {
 	switch {
 	case err == sql.ErrNoRows:
 		log.Errorf("checking user %v info: user not in database", user)
-		return CurrentUser{"-", -1, PrivLevelInvalid, TenantIDInvalid}
+		return CurrentUser{"-", -1, PrivLevelInvalid, TenantIDInvalid, []string{}}
 	case err != nil:
 		log.Errorf("Error checking user %v info: %v", user, err.Error())
-		return CurrentUser{"-", -1, PrivLevelInvalid, TenantIDInvalid}
+		return CurrentUser{"-", -1, PrivLevelInvalid, TenantIDInvalid, []string{}}
 	default:
 		return currentUserInfo
 	}
@@ -86,5 +88,5 @@ func GetCurrentUser(ctx context.Context) (*CurrentUser, error) {
 			return nil, fmt.Errorf("CurrentUser found with bad type: %T", v)
 		}
 	}
-	return &CurrentUser{"-", -1, PrivLevelInvalid, TenantIDInvalid}, errors.New("No user found in Context")
+	return &CurrentUser{"-", -1, PrivLevelInvalid, TenantIDInvalid, []string{}}, errors.New("No user found in Context")
 }
diff --git a/traffic_ops/traffic_ops_golang/crconfig/deliveryservice_test.go b/traffic_ops/traffic_ops_golang/crconfig/deliveryservice_test.go
index eede045..7bd30dd 100644
--- a/traffic_ops/traffic_ops_golang/crconfig/deliveryservice_test.go
+++ b/traffic_ops/traffic_ops_golang/crconfig/deliveryservice_test.go
@@ -311,7 +311,7 @@ func TestGetDSRegexesDomains(t *testing.T) {
 	}
 	defer db.Close()
 
-	cdn := "mycdn"
+	cdn := "mycdn"s
 	domain := "mycdn.invalid"
 
 	expectedMakeDSes := ExpectedMakeDSes()
diff --git a/traffic_ops/traffic_ops_golang/role/roles.go b/traffic_ops/traffic_ops_golang/role/roles.go
index 5d2f5ea..fd992e4 100644
--- a/traffic_ops/traffic_ops_golang/role/roles.go
+++ b/traffic_ops/traffic_ops_golang/role/roles.go
@@ -83,7 +83,19 @@ func (role TORole) Validate(db *sqlx.DB) []error {
 		"name":        validation.Validate(role.Name, validation.Required),
 		"description": validation.Validate(role.Description, validation.Required),
 		"privLevel":   validation.Validate(role.PrivLevel, validation.Required)}
-	return tovalidate.ToErrors(errs)
+
+	errsToReturn := tovalidate.ToErrors(errs)
+	checkCaps := `SELECT cap FROM UNNEST($1::text[]) AS cap WHERE NOT cap =  ANY(ARRAY(SELECT c.name FROM capability AS c WHERE c.name = ANY($1)))`
+	var badCaps []string
+	err := db.Select(&badCaps, checkCaps, pq.Array(role.Capabilities))
+	if err != nil {
+		log.Errorf("got error from selecting bad capabilities: %v", err)
+		return []error{tc.DBError}
+	}
+	if len(badCaps) > 0 {
+		errsToReturn = append(errsToReturn, fmt.Errorf("can not add non-existent capabilities: %v", badCaps))
+	}
+	return errsToReturn
 }
 
 //The TORole implementation of the Creator interface
@@ -110,7 +122,17 @@ func (role *TORole) Create(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErr
 		log.Error.Printf("could not begin transaction: %v", err)
 		return tc.DBError, tc.SystemError
 	}
-
+	if role.Capabilities != nil {
+	CapabilitiesLoop:
+		for _, cap := range *role.Capabilities {
+			for _, userCap := range user.Capabilities {
+				if cap == userCap {
+					continue CapabilitiesLoop
+				}
+			}
+			return errors.New("Can not create a role with a capability you do not have: " + cap), tc.ForbiddenError
+		}
+	}
 	resultRows, err := tx.NamedQuery(insertQuery(), role)
 	if err != nil {
 		if pqErr, ok := err.(*pq.Error); ok {
@@ -145,6 +167,12 @@ func (role *TORole) Create(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErr
 		return tc.DBError, tc.SystemError
 	}
 	role.SetKeys(map[string]interface{}{"id": id})
+	//after we have role ID we can associate the capabilities:
+	err, errType := role.createRoleCapabilityAssociations(tx)
+	if err != nil {
+		return err, errType
+	}
+
 	err = tx.Commit()
 	if err != nil {
 		log.Errorln("Could not commit transaction: ", err)
@@ -154,6 +182,38 @@ func (role *TORole) Create(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErr
 	return nil, tc.NoError
 }
 
+func (role *TORole) createRoleCapabilityAssociations(tx *sqlx.Tx) (error, tc.ApiErrorType) {
+	result, err := tx.Exec(associateCapabilities(), role.ID, pq.Array(role.Capabilities))
+	if err != nil {
+		log.Errorf("received non pq error: %++v from create execution", err)
+		return tc.DBError, tc.SystemError
+	}
+	rows, err := result.RowsAffected()
+	if err != nil {
+		log.Errorf("could not check result after inserting role_capability relations: %v", err)
+	}
+	expected := len(*role.Capabilities)
+	if int(rows) != expected {
+		log.Errorf("wrong number of role_capability rows created: %d expected: %d", rows, expected)
+	}
+	return nil, tc.NoError
+}
+
+func (role *TORole) deleteRoleCapabilityAssociations(tx *sqlx.Tx) (error, tc.ApiErrorType) {
+	result, err := tx.Exec(deleteAssociatedCapabilities(), role.ID)
+	if err != nil {
+
+		log.Errorf("received error: %++v from create execution", err)
+		return tc.DBError, tc.SystemError
+
+	}
+	_, err = result.RowsAffected()
+	if err != nil {
+		log.Errorf("could not check result after inserting role_capability relations: %v", err)
+	}
+	return nil, tc.NoError
+}
+
 func (role *TORole) Read(db *sqlx.DB, parameters map[string]string, user auth.CurrentUser) ([]interface{}, []error, tc.ApiErrorType) {
 	var rows *sqlx.Rows
 
@@ -180,12 +240,14 @@ func (role *TORole) Read(db *sqlx.DB, parameters map[string]string, user auth.Cu
 
 	Roles := []interface{}{}
 	for rows.Next() {
-		var s TORole
-		if err = rows.StructScan(&s); err != nil {
+		var r TORole
+		var caps []string
+		if err = rows.Scan(&r.ID, &r.Name, &r.Description, &r.PrivLevel, pq.Array(&caps)); err != nil {
 			log.Errorf("error parsing Role rows: %v", err)
 			return nil, []error{tc.DBError}, tc.SystemError
 		}
-		Roles = append(Roles, s)
+		r.Capabilities = &caps
+		Roles = append(Roles, r)
 	}
 
 	return Roles, []error{}, tc.NoError
@@ -214,6 +276,18 @@ func (role *TORole) Update(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErr
 		return tc.DBError, tc.SystemError
 	}
 
+	if role.Capabilities != nil {
+	CapabilitiesLoop:
+		for _, cap := range *role.Capabilities {
+			for _, userCap := range user.Capabilities {
+				if cap == userCap {
+					continue CapabilitiesLoop
+				}
+			}
+			return errors.New("Can not update a role with a capability you do not have: " + cap), tc.ForbiddenError
+		}
+	}
+
 	log.Debugf("about to run exec query: %s with role: %++v\n", updateQuery(), role)
 	result, err := tx.NamedExec(updateQuery(), role)
 	if err != nil {
@@ -241,6 +315,17 @@ func (role *TORole) Update(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErr
 			return fmt.Errorf("this update affected too many rows: %d", rowsAffected), tc.SystemError
 		}
 	}
+	//remove associations
+	err, errType := role.deleteRoleCapabilityAssociations(tx)
+	if err != nil {
+		return err, errType
+	}
+	//create new associations
+	err, errType = role.createRoleCapabilityAssociations(tx)
+	if err != nil {
+		return err, errType
+	}
+
 	err = tx.Commit()
 	if err != nil {
 		log.Errorln("Could not commit transaction: ", err)
@@ -296,6 +381,12 @@ func (role *TORole) Delete(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErr
 			return fmt.Errorf("this create affected too many rows: %d", rowsAffected), tc.SystemError
 		}
 	}
+	//remove associations
+	err, errType := role.deleteRoleCapabilityAssociations(tx)
+	if err != nil {
+		return err, errType
+	}
+
 	err = tx.Commit()
 	if err != nil {
 		log.Errorln("Could not commit transaction: ", err)
@@ -310,7 +401,8 @@ func selectQuery() string {
 id,
 name,
 description,
-priv_level
+priv_level,
+ARRAY(SELECT rc.cap_name FROM role_capability AS rc WHERE rc.role_id=id) AS capabilities
 
 FROM role`
 	return query
@@ -325,6 +417,22 @@ WHERE id=:id`
 	return query
 }
 
+func deleteAssociatedCapabilities() string {
+	query := `DELETE FROM role_capability
+WHERE role_id=$1`
+	return query
+}
+
+func associateCapabilities() string {
+	query := `INSERT INTO role_capability (
+role_id,
+cap_name) WITH
+	q1 AS ( SELECT * FROM (VALUES ($1::bigint)) AS role_id ),
+	q2 AS (SELECT UNNEST($2::text[]))
+	SELECT * FROM q1,q2`
+	return query
+}
+
 func insertQuery() string {
 	query := `INSERT INTO role (
 name,
diff --git a/traffic_ops/traffic_ops_golang/role/roles_test.go b/traffic_ops/traffic_ops_golang/role/roles_test.go
index 44469ac..ec3dfe6 100644
--- a/traffic_ops/traffic_ops_golang/role/roles_test.go
+++ b/traffic_ops/traffic_ops_golang/role/roles_test.go
@@ -141,7 +141,6 @@ func TestValidate(t *testing.T) {
 	expectedErrs := []error{
 		errors.New(`'description' cannot be blank`),
 		errors.New(`'privLevel' cannot be blank`),
-
 	}
 
 	if !reflect.DeepEqual(expectedErrs, errs) {
@@ -149,7 +148,7 @@ func TestValidate(t *testing.T) {
 	}
 
 	//  name,  domainname both valid
-	r = TORole{Name: stringAddr("this is a valid name"), Description: stringAddr("this is a description"),PrivLevel:intAddr(30),}
+	r = TORole{Name: stringAddr("this is a valid name"), Description: stringAddr("this is a description"), PrivLevel: intAddr(30)}
 	expectedErrs = []error{}
 	errs = r.Validate(nil)
 	if !reflect.DeepEqual(expectedErrs, errs) {
diff --git a/traffic_ops/traffic_ops_golang/routing.go b/traffic_ops/traffic_ops_golang/routing.go
index 7a36a46..c01239f 100644
--- a/traffic_ops/traffic_ops_golang/routing.go
+++ b/traffic_ops/traffic_ops_golang/routing.go
@@ -215,7 +215,7 @@ func RegisterRoutes(d ServerData) error {
 }
 
 func prepareUserInfoStmt(db *sqlx.DB) (*sqlx.Stmt, error) {
-	return db.Preparex("SELECT r.priv_level, u.id, u.username, COALESCE(u.tenant_id, -1) AS tenant_id FROM tm_user AS u JOIN role AS r ON u.role = r.id WHERE u.username = $1")
+	return db.Preparex("SELECT r.priv_level, u.id, u.username, COALESCE(u.tenant_id, -1) AS tenant_id, ARRAY(SELECT rc.cap_name FROM role_capability AS rc WHERE rc.role_id=r.id) AS capabilities FROM tm_user AS u JOIN role AS r ON u.role = r.id WHERE u.username = $1")
 }
 
 func use(h http.HandlerFunc, middlewares []Middleware) http.HandlerFunc {

-- 
To stop receiving notification emails like this one, please contact
mitchell852@apache.org.