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.