You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@trafficcontrol.apache.org by GitBox <gi...@apache.org> on 2021/08/19 20:00:26 UTC
[GitHub] [trafficcontrol] ocket8888 commented on a change in pull request #6124: Role and User struct changes for permission based roles
ocket8888 commented on a change in pull request #6124:
URL: https://github.com/apache/trafficcontrol/pull/6124#discussion_r692377505
##########
File path: lib/go-tc/roles.go
##########
@@ -19,6 +19,55 @@ package tc
* under the License.
*/
+// RolesResponseV5 is a list of RoleV50 as a response.
+type RolesResponseV5 struct {
+ Response []RoleV50 `json:"response"`
+ Alerts
+}
+
+// RoleResponseV5 is a RoleV50 as a response.
+type RoleResponseV5 struct {
+ Response RoleV50 `json:"response"`
+ Alerts
+}
+
+// RoleV50 is the structure used to depict roles in API v5.0
Review comment:
GoDoc should end with punctuation
##########
File path: lib/go-tc/users.go
##########
@@ -33,6 +33,249 @@ import (
"github.com/go-ozzo/ozzo-validation/is"
)
+// A UserV50 is a representation of a Traffic Ops user as it appears in version
+// 5.0 of Traffic Ops's API.
+type UserV50 struct {
+ AddressLine1 *string `json:"addressLine1" db:"address_line1"`
+ AddressLine2 *string `json:"addressLine2" db:"address_line2"`
+ ChangeLogCount *int `json:"changeLogCount" db:"change_log_count"`
+ City *string `json:"city" db:"city"`
+ Company *string `json:"company" db:"company"`
+ ConfirmLocalPassword *string `json:"confirmLocalPasswd,omitempty" db:"confirm_local_passwd"`
+ Country *string `json:"country" db:"country"`
+ Email *string `json:"email" db:"email"`
+ FullName *string `json:"fullName" db:"full_name"`
+ GID *int `json:"gid"`
+ ID *int `json:"id" db:"id"`
+ LastAuthenticated time.Time `json:"lastAuthenticated" db:"last_authenticated"`
+ LastUpdated time.Time `json:"lastUpdated" db:"last_updated"`
+ LocalPassword *string `json:"localPasswd,omitempty" db:"local_passwd"`
+ NewUser bool `json:"newUser" db:"new_user"`
+ PhoneNumber *string `json:"phoneNumber" db:"phone_number"`
+ PostalCode *string `json:"postalCode" db:"postal_code"`
+ PublicSSHKey *string `json:"publicSshKey" db:"public_ssh_key"`
+ RegistrationSent *time.Time `json:"registrationSent" db:"registration_sent"`
+ Role string `json:"role" db:"role"`
+ StateOrProvince *string `json:"stateOrProvince" db:"state_or_province"`
+ Tenant *string `json:"tenant"`
+ TenantID int `json:"tenantId" db:"tenant_id"`
+ Token *string `json:"-" db:"token"`
+ UID *int `json:"uid"`
+ Username string `json:"username" db:"username"`
+}
+
+// UsersResponseV50 is the type of a response from Traffic Ops to requests made
+// to /users which return more than one user.
+type UsersResponseV50 struct {
+ Response []UserV50 `json:"response"`
+ Alerts
+}
+
+// UserResponseV50 is the type of a response from Traffic Ops to requests made
+// to /users which return one user.
+type UserResponseV50 struct {
+ Response UserV50 `json:"response"`
+ Alerts
+}
Review comment:
same as above RE: V5 type allias
##########
File path: lib/go-tc/roles.go
##########
@@ -19,6 +19,55 @@ package tc
* under the License.
*/
+// RolesResponseV5 is a list of RoleV50 as a response.
+type RolesResponseV5 struct {
+ Response []RoleV50 `json:"response"`
+ Alerts
+}
+
+// RoleResponseV5 is a RoleV50 as a response.
+type RoleResponseV5 struct {
+ Response RoleV50 `json:"response"`
+ Alerts
+}
+
+// RoleV50 is the structure used to depict roles in API v5.0
+type RoleV50 struct {
+ Name *string `json:"name" db:"name"`
+ Permissions []string `json:"permissions" db:"permissions"`
+ Description *string `json:"description" db:"description"`
+ LastUpdated *TimeNoMod `json:"lastUpdated,omitempty" db:"last_updated"`
+}
Review comment:
We should have a type for `RoleV5` that, for now, is just an alias of `RoleV50`, and `RolesResponseV5` and `RoleResponseV5` should use that instead of `RoleV50`. That way, if we have to make a `RoleV51` we can update the alias and don't need to make breaking changes to Go client call signatures.
##########
File path: traffic_ops/testing/api/v4/cdn_locks_test.go
##########
@@ -219,8 +218,7 @@ func AdminCdnLocks(t *testing.T) {
user2.FullName = util.StrPtr("firstName2 LastName2")
_, _, err = TOSession.CreateUser(user2, client.RequestOptions{})
if err != nil {
- fmt.Println(err)
- t.Fatalf("could not create test user with username: %s", *user2.Username)
+ t.Fatalf("could not create test user with username: %s, err: %v", *user2.Username, err.Error())
Review comment:
nit but you don't need to call `.Error` when you're formatting with `%v`
##########
File path: lib/go-tc/users.go
##########
@@ -334,6 +665,15 @@ type UserRegistrationRequest struct {
TenantID uint `json:"tenantId"`
}
+// UserRegistrationRequestV50 is the request submitted by operators when they want to register a new
+// user in api V5.
+type UserRegistrationRequestV50 struct {
+ Email rfc.EmailAddress `json:"email"`
+ // Role - despite being named "Role" - is actually merely the *ID* of a Role to give the new user.
+ Role string `json:"role"`
+ TenantID uint `json:"tenantId"`
+}
+
Review comment:
Same as above RE: V5 alias
##########
File path: lib/go-tc/roles.go
##########
@@ -19,6 +19,55 @@ package tc
* under the License.
*/
+// RolesResponseV5 is a list of RoleV50 as a response.
+type RolesResponseV5 struct {
+ Response []RoleV50 `json:"response"`
+ Alerts
+}
+
+// RoleResponseV5 is a RoleV50 as a response.
+type RoleResponseV5 struct {
+ Response RoleV50 `json:"response"`
+ Alerts
+}
+
+// RoleV50 is the structure used to depict roles in API v5.0
+type RoleV50 struct {
+ Name *string `json:"name" db:"name"`
+ Permissions []string `json:"permissions" db:"permissions"`
+ Description *string `json:"description" db:"description"`
+ LastUpdated *TimeNoMod `json:"lastUpdated,omitempty" db:"last_updated"`
Review comment:
Since `Name` and `Description` are not actually allowed to be `null` or undefined, do those need to be pointers?
Also, we should use a `time.Time` here instead of a `TimeNoMod` so that we can have RFC3339-formatted timestamps as per the API guidelines.
##########
File path: lib/go-tc/users.go
##########
@@ -33,6 +33,249 @@ import (
"github.com/go-ozzo/ozzo-validation/is"
)
+// A UserV50 is a representation of a Traffic Ops user as it appears in version
+// 5.0 of Traffic Ops's API.
+type UserV50 struct {
+ AddressLine1 *string `json:"addressLine1" db:"address_line1"`
+ AddressLine2 *string `json:"addressLine2" db:"address_line2"`
+ ChangeLogCount *int `json:"changeLogCount" db:"change_log_count"`
+ City *string `json:"city" db:"city"`
+ Company *string `json:"company" db:"company"`
+ ConfirmLocalPassword *string `json:"confirmLocalPasswd,omitempty" db:"confirm_local_passwd"`
+ Country *string `json:"country" db:"country"`
+ Email *string `json:"email" db:"email"`
+ FullName *string `json:"fullName" db:"full_name"`
+ GID *int `json:"gid"`
+ ID *int `json:"id" db:"id"`
+ LastAuthenticated time.Time `json:"lastAuthenticated" db:"last_authenticated"`
+ LastUpdated time.Time `json:"lastUpdated" db:"last_updated"`
+ LocalPassword *string `json:"localPasswd,omitempty" db:"local_passwd"`
+ NewUser bool `json:"newUser" db:"new_user"`
+ PhoneNumber *string `json:"phoneNumber" db:"phone_number"`
+ PostalCode *string `json:"postalCode" db:"postal_code"`
+ PublicSSHKey *string `json:"publicSshKey" db:"public_ssh_key"`
+ RegistrationSent *time.Time `json:"registrationSent" db:"registration_sent"`
+ Role string `json:"role" db:"role"`
+ StateOrProvince *string `json:"stateOrProvince" db:"state_or_province"`
+ Tenant *string `json:"tenant"`
+ TenantID int `json:"tenantId" db:"tenant_id"`
+ Token *string `json:"-" db:"token"`
+ UID *int `json:"uid"`
+ Username string `json:"username" db:"username"`
+}
+
+// UsersResponseV50 is the type of a response from Traffic Ops to requests made
+// to /users which return more than one user.
+type UsersResponseV50 struct {
+ Response []UserV50 `json:"response"`
+ Alerts
+}
+
+// UserResponseV50 is the type of a response from Traffic Ops to requests made
+// to /users which return one user.
+type UserResponseV50 struct {
+ Response UserV50 `json:"response"`
+ Alerts
+}
+
+// copyStringIfNotNil makes a deep copy of s - unless it's nil, in which case it
+// just returns nil.
+func copyStringIfNotNil(s *string) *string {
+ if s == nil {
+ return nil
+ }
+ ret := new(string)
+ *ret = *s
+ return ret
+}
+
+// copyIntIfNotNil makes a deep copy of i - unless it's nil, in which case it
+// just returns nil.
+func copyIntIfNotNil(i *int) *int {
+ if i == nil {
+ return nil
+ }
+ ret := new(int)
+ *ret = *i
+ return ret
+}
+
+// UpgradeFromLegacyUser converts a User to a UserV50 (as seen in API versions 5.x)
+func (u User) UpgradeFromLegacyUser() UserV50 {
Review comment:
nit: Since this can be only be called from a `User`, we already know it's upgrading "from a legacy user", those three words make the name longer without giving us any extra information. Could just be `Upgrade`.
##########
File path: lib/go-tc/users.go
##########
@@ -105,6 +348,33 @@ type UserCurrentV40 struct {
LastAuthenticated *time.Time `json:"lastAuthenticated" db:"last_authenticated"`
}
+// UserCurrentV50 represents the structure for the "current" user, and has the "Role" field as a *string, as opposed to a *int found in the older versions
+type UserCurrentV50 struct {
+ UserName *string `json:"username"`
+ LocalUser *bool `json:"localUser"`
+ AddressLine1 *string `json:"addressLine1"`
+ AddressLine2 *string `json:"addressLine2"`
+ City *string `json:"city"`
+ Company *string `json:"company"`
+ Country *string `json:"country"`
+ Email *string `json:"email"`
+ FullName *string `json:"fullName"`
+ GID *int `json:"gid"`
+ ID *int `json:"id"`
+ NewUser *bool `json:"newUser"`
+ PhoneNumber *string `json:"phoneNumber"`
+ PostalCode *string `json:"postalCode"`
+ PublicSSHKey *string `json:"publicSshKey"`
+ Role *string `json:"role"`
+ StateOrProvince *string `json:"stateOrProvince"`
+ Tenant *string `json:"tenant"`
+ TenantID *int `json:"tenantId"`
+ Token *string `json:"-"`
+ UID *int `json:"uid"`
+ LastUpdated *time.Time `json:"lastUpdated"`
+ LastAuthenticated *time.Time `json:"lastAuthenticated"`
Review comment:
`UserV50` has non-pointer types for `LastUpdated`, `LastAuthenticated`, `Role`, and `UserName`, and I think those ought to match here
##########
File path: lib/go-tc/users.go
##########
@@ -33,6 +33,249 @@ import (
"github.com/go-ozzo/ozzo-validation/is"
)
+// A UserV50 is a representation of a Traffic Ops user as it appears in version
+// 5.0 of Traffic Ops's API.
+type UserV50 struct {
+ AddressLine1 *string `json:"addressLine1" db:"address_line1"`
+ AddressLine2 *string `json:"addressLine2" db:"address_line2"`
+ ChangeLogCount *int `json:"changeLogCount" db:"change_log_count"`
+ City *string `json:"city" db:"city"`
+ Company *string `json:"company" db:"company"`
+ ConfirmLocalPassword *string `json:"confirmLocalPasswd,omitempty" db:"confirm_local_passwd"`
+ Country *string `json:"country" db:"country"`
+ Email *string `json:"email" db:"email"`
+ FullName *string `json:"fullName" db:"full_name"`
+ GID *int `json:"gid"`
+ ID *int `json:"id" db:"id"`
+ LastAuthenticated time.Time `json:"lastAuthenticated" db:"last_authenticated"`
+ LastUpdated time.Time `json:"lastUpdated" db:"last_updated"`
+ LocalPassword *string `json:"localPasswd,omitempty" db:"local_passwd"`
+ NewUser bool `json:"newUser" db:"new_user"`
+ PhoneNumber *string `json:"phoneNumber" db:"phone_number"`
+ PostalCode *string `json:"postalCode" db:"postal_code"`
+ PublicSSHKey *string `json:"publicSshKey" db:"public_ssh_key"`
+ RegistrationSent *time.Time `json:"registrationSent" db:"registration_sent"`
+ Role string `json:"role" db:"role"`
+ StateOrProvince *string `json:"stateOrProvince" db:"state_or_province"`
+ Tenant *string `json:"tenant"`
+ TenantID int `json:"tenantId" db:"tenant_id"`
+ Token *string `json:"-" db:"token"`
+ UID *int `json:"uid"`
+ Username string `json:"username" db:"username"`
+}
+
+// UsersResponseV50 is the type of a response from Traffic Ops to requests made
+// to /users which return more than one user.
+type UsersResponseV50 struct {
+ Response []UserV50 `json:"response"`
+ Alerts
+}
+
+// UserResponseV50 is the type of a response from Traffic Ops to requests made
+// to /users which return one user.
+type UserResponseV50 struct {
+ Response UserV50 `json:"response"`
+ Alerts
+}
+
+// copyStringIfNotNil makes a deep copy of s - unless it's nil, in which case it
+// just returns nil.
+func copyStringIfNotNil(s *string) *string {
+ if s == nil {
+ return nil
+ }
+ ret := new(string)
+ *ret = *s
+ return ret
+}
+
+// copyIntIfNotNil makes a deep copy of i - unless it's nil, in which case it
+// just returns nil.
+func copyIntIfNotNil(i *int) *int {
+ if i == nil {
+ return nil
+ }
+ ret := new(int)
+ *ret = *i
+ return ret
+}
+
+// UpgradeFromLegacyUser converts a User to a UserV50 (as seen in API versions 5.x)
+func (u User) UpgradeFromLegacyUser() UserV50 {
+ var ret UserV50
+ ret.AddressLine1 = copyStringIfNotNil(u.AddressLine1)
+ ret.AddressLine2 = copyStringIfNotNil(u.AddressLine2)
+ ret.City = copyStringIfNotNil(u.City)
+ ret.Company = copyStringIfNotNil(u.Company)
+ ret.ConfirmLocalPassword = copyStringIfNotNil(u.ConfirmLocalPassword)
+ ret.Country = copyStringIfNotNil(u.Country)
+ ret.Email = copyStringIfNotNil(u.Email)
+ ret.GID = copyIntIfNotNil(u.GID)
+ ret.ID = copyIntIfNotNil(u.ID)
+ ret.LocalPassword = copyStringIfNotNil(u.LocalPassword)
+ ret.PhoneNumber = copyStringIfNotNil(u.PhoneNumber)
+ ret.PostalCode = copyStringIfNotNil(u.PostalCode)
+ ret.PublicSSHKey = copyStringIfNotNil(u.PublicSSHKey)
+ ret.StateOrProvince = copyStringIfNotNil(u.StateOrProvince)
+ ret.Tenant = copyStringIfNotNil(u.Tenant)
+ ret.Token = copyStringIfNotNil(u.Token)
+ ret.UID = copyIntIfNotNil(u.UID)
+ ret.FullName = u.FullName
+ if u.LastUpdated != nil {
+ ret.LastUpdated = u.LastUpdated.Time
+ }
+ if u.NewUser != nil {
+ ret.NewUser = *u.NewUser
+ }
+ if u.RegistrationSent != nil {
+ ret.RegistrationSent = new(time.Time)
+ *ret.RegistrationSent = u.RegistrationSent.Time
+ }
+ if u.RoleName != nil {
+ ret.Role = *u.RoleName
+ }
+ if u.TenantID != nil {
+ ret.TenantID = *u.TenantID
+ }
+ if u.Username != nil {
+ ret.Username = *u.Username
+ }
+ return ret
+}
+
+// UpgradeFromUserV40 converts a UserV40 to a UserV50 (as seen in API versions 5.x)
+func (u UserV40) UpgradeFromUserV40() UserV50 {
+ var ret UserV50
+ ret.AddressLine1 = copyStringIfNotNil(u.AddressLine1)
+ ret.AddressLine2 = copyStringIfNotNil(u.AddressLine2)
+ ret.City = copyStringIfNotNil(u.City)
+ ret.Company = copyStringIfNotNil(u.Company)
+ ret.ConfirmLocalPassword = copyStringIfNotNil(u.ConfirmLocalPassword)
+ ret.Country = copyStringIfNotNil(u.Country)
+ ret.Email = copyStringIfNotNil(u.Email)
+ ret.GID = copyIntIfNotNil(u.GID)
+ ret.ID = copyIntIfNotNil(u.ID)
+ ret.LocalPassword = copyStringIfNotNil(u.LocalPassword)
+ ret.PhoneNumber = copyStringIfNotNil(u.PhoneNumber)
+ ret.PostalCode = copyStringIfNotNil(u.PostalCode)
+ ret.PublicSSHKey = copyStringIfNotNil(u.PublicSSHKey)
+ ret.StateOrProvince = copyStringIfNotNil(u.StateOrProvince)
+ ret.Tenant = copyStringIfNotNil(u.Tenant)
+ ret.Token = copyStringIfNotNil(u.Token)
+ ret.UID = copyIntIfNotNil(u.UID)
+ ret.ChangeLogCount = copyIntIfNotNil(u.ChangeLogCount)
+ if u.LastAuthenticated != nil {
+ ret.LastAuthenticated = *u.LastAuthenticated
+ }
+ ret.FullName = u.FullName
+ if u.LastUpdated != nil {
+ ret.LastUpdated = u.LastUpdated.Time
+ }
+ if u.NewUser != nil {
+ ret.NewUser = *u.NewUser
+ }
+ if u.RegistrationSent != nil {
+ ret.RegistrationSent = new(time.Time)
+ *ret.RegistrationSent = u.RegistrationSent.Time
+ }
+ if u.RoleName != nil {
+ ret.Role = *u.RoleName
+ }
+ if u.TenantID != nil {
+ ret.TenantID = *u.TenantID
+ }
+ if u.Username != nil {
+ ret.Username = *u.Username
+ }
+ return ret
+}
+
+// Downgrade converts a UserV50 to a UserV40 (as seen in API versions 4.x) and User (as seen in API versions < 4.0)
+func (u UserV50) Downgrade() (UserV40, User) {
Review comment:
If we already have a `DowngradeToLegacy`, why the second return value for a legacy `User` here? Would it help if, instead of having ways to upgrade from/downgrade to a regular `User` straight from a `UserV40` if there was a `UserV50.Downgrade` that downgrades to `UserV40`, a `UserV40.Downgrade` that downgrades a V40 to a `User`, a `User.Upgrade` that upgrades a `User` to V40, and a `UserV40.Upgrade` that upgrades a `UserV40` to V5?
That way, if you need a UserV40 and a User generated from the same UserV50, then you can do `userV4 = userV5.Downgrade()` and then when you need a `User` you can just `Downgrade` again.
##########
File path: lib/go-tc/users.go
##########
@@ -33,6 +33,249 @@ import (
"github.com/go-ozzo/ozzo-validation/is"
)
+// A UserV50 is a representation of a Traffic Ops user as it appears in version
+// 5.0 of Traffic Ops's API.
+type UserV50 struct {
+ AddressLine1 *string `json:"addressLine1" db:"address_line1"`
+ AddressLine2 *string `json:"addressLine2" db:"address_line2"`
+ ChangeLogCount *int `json:"changeLogCount" db:"change_log_count"`
+ City *string `json:"city" db:"city"`
+ Company *string `json:"company" db:"company"`
+ ConfirmLocalPassword *string `json:"confirmLocalPasswd,omitempty" db:"confirm_local_passwd"`
+ Country *string `json:"country" db:"country"`
+ Email *string `json:"email" db:"email"`
+ FullName *string `json:"fullName" db:"full_name"`
+ GID *int `json:"gid"`
+ ID *int `json:"id" db:"id"`
+ LastAuthenticated time.Time `json:"lastAuthenticated" db:"last_authenticated"`
+ LastUpdated time.Time `json:"lastUpdated" db:"last_updated"`
+ LocalPassword *string `json:"localPasswd,omitempty" db:"local_passwd"`
+ NewUser bool `json:"newUser" db:"new_user"`
+ PhoneNumber *string `json:"phoneNumber" db:"phone_number"`
+ PostalCode *string `json:"postalCode" db:"postal_code"`
+ PublicSSHKey *string `json:"publicSshKey" db:"public_ssh_key"`
+ RegistrationSent *time.Time `json:"registrationSent" db:"registration_sent"`
+ Role string `json:"role" db:"role"`
+ StateOrProvince *string `json:"stateOrProvince" db:"state_or_province"`
+ Tenant *string `json:"tenant"`
+ TenantID int `json:"tenantId" db:"tenant_id"`
+ Token *string `json:"-" db:"token"`
+ UID *int `json:"uid"`
+ Username string `json:"username" db:"username"`
+}
+
+// UsersResponseV50 is the type of a response from Traffic Ops to requests made
+// to /users which return more than one user.
+type UsersResponseV50 struct {
+ Response []UserV50 `json:"response"`
+ Alerts
+}
+
+// UserResponseV50 is the type of a response from Traffic Ops to requests made
+// to /users which return one user.
+type UserResponseV50 struct {
+ Response UserV50 `json:"response"`
+ Alerts
+}
+
+// copyStringIfNotNil makes a deep copy of s - unless it's nil, in which case it
+// just returns nil.
+func copyStringIfNotNil(s *string) *string {
+ if s == nil {
+ return nil
+ }
+ ret := new(string)
+ *ret = *s
+ return ret
+}
+
+// copyIntIfNotNil makes a deep copy of i - unless it's nil, in which case it
+// just returns nil.
+func copyIntIfNotNil(i *int) *int {
+ if i == nil {
+ return nil
+ }
+ ret := new(int)
+ *ret = *i
+ return ret
+}
+
+// UpgradeFromLegacyUser converts a User to a UserV50 (as seen in API versions 5.x)
+func (u User) UpgradeFromLegacyUser() UserV50 {
+ var ret UserV50
+ ret.AddressLine1 = copyStringIfNotNil(u.AddressLine1)
+ ret.AddressLine2 = copyStringIfNotNil(u.AddressLine2)
+ ret.City = copyStringIfNotNil(u.City)
+ ret.Company = copyStringIfNotNil(u.Company)
+ ret.ConfirmLocalPassword = copyStringIfNotNil(u.ConfirmLocalPassword)
+ ret.Country = copyStringIfNotNil(u.Country)
+ ret.Email = copyStringIfNotNil(u.Email)
+ ret.GID = copyIntIfNotNil(u.GID)
+ ret.ID = copyIntIfNotNil(u.ID)
+ ret.LocalPassword = copyStringIfNotNil(u.LocalPassword)
+ ret.PhoneNumber = copyStringIfNotNil(u.PhoneNumber)
+ ret.PostalCode = copyStringIfNotNil(u.PostalCode)
+ ret.PublicSSHKey = copyStringIfNotNil(u.PublicSSHKey)
+ ret.StateOrProvince = copyStringIfNotNil(u.StateOrProvince)
+ ret.Tenant = copyStringIfNotNil(u.Tenant)
+ ret.Token = copyStringIfNotNil(u.Token)
+ ret.UID = copyIntIfNotNil(u.UID)
+ ret.FullName = u.FullName
+ if u.LastUpdated != nil {
+ ret.LastUpdated = u.LastUpdated.Time
+ }
+ if u.NewUser != nil {
+ ret.NewUser = *u.NewUser
+ }
+ if u.RegistrationSent != nil {
+ ret.RegistrationSent = new(time.Time)
+ *ret.RegistrationSent = u.RegistrationSent.Time
+ }
+ if u.RoleName != nil {
+ ret.Role = *u.RoleName
+ }
+ if u.TenantID != nil {
+ ret.TenantID = *u.TenantID
+ }
+ if u.Username != nil {
+ ret.Username = *u.Username
+ }
+ return ret
+}
+
+// UpgradeFromUserV40 converts a UserV40 to a UserV50 (as seen in API versions 5.x)
+func (u UserV40) UpgradeFromUserV40() UserV50 {
Review comment:
nit: Since this can be only be called from a `UserV40`, we already know it's upgrading "from a UserV40", those three words make the name longer without giving us any extra information. Could just be `Upgrade`.
##########
File path: lib/go-tc/users.go
##########
@@ -136,9 +406,49 @@ type CurrentUserUpdateRequestUser struct {
Username json.RawMessage `json:"username"`
}
+func (u UserCurrentV40) UpgradeUserCurrent() UserCurrentV50 {
Review comment:
GoDoc?
Also, nit: Since this can be only be called from a `UserCurrentV40`, we already know it's upgrading "from a UserCurrentV40", those two words make the name longer without giving us any extra information. Could just be `Upgrade`.
##########
File path: traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
##########
@@ -1603,3 +1624,35 @@ func GetDSIDFromStaticDNSEntry(tx *sql.Tx, staticDNSEntryID int) (int, error) {
}
return dsID, nil
}
+
+// AppendWhere appends 'extra' safely to the WHERE clause 'where'. What is
+// returned is guaranteed to be a valid WHERE clause (including a blank string).
+func AppendWhere(where, extra string) string {
+ if where == "" && extra == "" {
+ return ""
+ }
+ if where == "" {
+ where = BaseWhere + " "
+ } else {
+ where += " AND "
+ }
+ return where + extra
+}
+
+// GetRoleIDFromName returns the ID of the role associated with the supplied name
Review comment:
GoDoc should end with punctuation.
Also, most functions like this return a `bool` indicating whether or not the identified object could be found, should this one?
##########
File path: traffic_ops/traffic_ops_golang/role/roles.go
##########
@@ -273,3 +291,522 @@ RETURNING id, last_updated`
func deleteQuery() string {
return `DELETE FROM role WHERE id = :id`
}
+
+func Update(w http.ResponseWriter, r *http.Request) {
+ var roleID int
+ var roleName string
+ var roleDesc string
+ var privLevel int
+ var roleCapabilities *[]string
+ var roleV50 tc.RoleV50
+ var role tc.Role
+ var ok bool
+ var err error
+
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ version := inf.Version
+ if version == nil {
+ middleware.NotImplementedHandler().ServeHTTP(w, r)
+ return
+ }
+ tx := inf.Tx.Tx
+ if version.Major >= 5 {
+ if err := json.NewDecoder(r.Body).Decode(&roleV50); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+ if err := Validate(inf.Tx, role, roleV50, version); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+ roleDesc = *roleV50.Description
+ roleCapabilities = &roleV50.Permissions
+ if roleName, ok = inf.Params["name"]; !ok {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+ roleID, err = dbhelpers.GetRoleIDFromName(tx, roleName)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("no ID exists for the supplied role name"), nil)
+ return
+ }
+
+ existingLastUpdated, found, err := api.GetLastUpdated(inf.Tx, roleID, "role")
+ if err == nil && found == false {
+ api.HandleErr(w, r, tx, http.StatusNotFound, errors.New("no role found with that ID"), nil)
+ return
+ }
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err)
+ return
+ }
+
+ if !api.IsUnmodified(r.Header, *existingLastUpdated) {
+ api.HandleErr(w, r, tx, http.StatusPreconditionFailed, api.ResourceModifiedError, nil)
+ return
+ }
+ rows, err := tx.Query(updateRoleQuery(), roleDesc, roleName)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("updating role: "+err.Error()))
+ return
+ }
+ defer rows.Close()
+ if !rows.Next() {
+ api.HandleErr(w, r, tx, http.StatusNotFound, errors.New("no role found with this ID"), nil)
+ return
+ }
+ lastUpdated := tc.TimeNoMod{}
+ for rows.Next() {
+ if err := rows.Scan(&lastUpdated); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("scanning lastUpdated from role update: "+err.Error()))
+ return
+ }
+ roleV50.LastUpdated = &lastUpdated
+ }
+ }
+ //else {
+ // if err := json.NewDecoder(r.Body).Decode(&role); err != nil {
+ // api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ // return
+ // }
+ // if err := Validate(inf.Tx, role, roleV50, version); err != nil {
+ // api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("name and/or description and/or privLevel can not be empty"), nil)
+ // return
+ // }
+ // roleName = *role.Name
+ // roleDesc = *role.Description
+ // privLevel = *role.PrivLevel
+ // roleCapabilities = role.Capabilities
+ // if roleIDParam, ok := inf.Params["id"]; !ok {
+ // api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("must supply a role ID to update"), nil)
+ // return
+ // } else {
+ // roleID, err = strconv.Atoi(roleIDParam)
+ // if err != nil {
+ // api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("must supply an integral role ID to update"), nil)
+ // return
+ // }
+ // }
+ // if privLevel > inf.User.PrivLevel {
+ // api.HandleErr(w, r, tx, http.StatusForbidden, errors.New("can not create a role with a higher priv level than your own"), nil)
+ // return
+ // }
+ // existingLastUpdated, found, err := api.GetLastUpdated(inf.Tx, roleID, "role")
+ // if err == nil && found == false {
+ // api.HandleErr(w, r, tx, http.StatusNotFound, errors.New("no role found with that ID"), nil)
+ // return
+ // }
+ // if err != nil {
+ // api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err)
+ // return
+ // }
+ //
+ // if !api.IsUnmodified(r.Header, *existingLastUpdated) {
+ // api.HandleErr(w, r, tx, http.StatusPreconditionFailed, api.ResourceModifiedError, nil)
+ // return
+ // }
+ //
+ // rows, err := tx.Query(updateLegacyRoleQuery(), roleName, roleDesc, roleID)
+ // if err != nil {
+ // api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("updating role here: "+err.Error()))
+ // return
+ // }
+ // rows.Close()
+ //}
+
+ if roleCapabilities != nil && *roleCapabilities != nil {
+ userErr, sysErr, errCode = deleteRoleCapabilityAssociations(inf.Tx, roleID)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ userErr, sysErr, errCode = createRoleCapabilityAssociations(inf.Tx, roleID, roleCapabilities)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ }
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "role was updated.")
+ var roleResponse interface{}
+ if version.Major >= 5 {
+ var capabilities []string
+ if roleCapabilities != nil {
+ capabilities = *roleCapabilities
+ }
+ roleResponse = tc.RoleV50{
+ Name: util.StrPtr(roleName),
+ Permissions: capabilities,
+ Description: util.StrPtr(roleDesc),
+ }
+ } else {
+ roleResponse = tc.Role{
+ RoleV11: tc.RoleV11{
+ ID: util.IntPtr(roleID),
+ Name: util.StrPtr(roleName),
+ Description: util.StrPtr(roleDesc),
+ PrivLevel: util.IntPtr(privLevel),
+ },
+ Capabilities: roleCapabilities,
+ }
+ }
+ api.WriteAlertsObj(w, r, http.StatusOK, alerts, roleResponse)
+}
+
+func deleteRoleQuery() string {
+ return `DELETE FROM role WHERE id = $1`
+}
+
+func readQuery() string {
+ return `SELECT
+id,
+name,
+description,
+priv_level,
+last_updated,
+ARRAY(SELECT rc.cap_name FROM role_capability AS rc WHERE rc.role_id=id) AS permissions
+FROM role`
+}
+
+func createQuery() string {
+ return `INSERT INTO role (
+name,
+description,
+priv_level
+) VALUES (
+$1,
+$2,
+$3
+)
+RETURNING id, last_updated`
+}
+
+func createRoleCapabilityAssociations(tx *sqlx.Tx, roleID int, permissions *[]string) (error, error, int) {
+ result, err := tx.Exec(associateCapabilities(), roleID, pq.Array(permissions))
+ if err != nil {
+ return nil, errors.New("creating role capabilities: " + err.Error()), http.StatusInternalServerError
+ }
+
+ if rows, err := result.RowsAffected(); err != nil {
+ log.Errorf("could not check result after inserting role_capability relations: %v", err)
+ } else if expected := len(*permissions); int(rows) != expected {
+ log.Errorf("wrong number of role_capability rows created: %d expected: %d", rows, expected)
+ }
+ return nil, nil, http.StatusOK
+}
+
+func Validate(tx *sqlx.Tx, role tc.Role, roleV50 tc.RoleV50, version *api.Version) error {
Review comment:
Does this need to be exported out of the package?
Instead of using different arguments based on the API version, could we not just have a different validation func for each version of a Role? I think what you're trying to do here is something like
```typescript
export function Validate(tx: sqlx.Tx, role: tc.Role | tc.RoleV50, version: api.Version) {
```
but the reality is that things like that don't work well in Go.
##########
File path: traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
##########
@@ -1603,3 +1624,35 @@ func GetDSIDFromStaticDNSEntry(tx *sql.Tx, staticDNSEntryID int) (int, error) {
}
return dsID, nil
}
+
+// AppendWhere appends 'extra' safely to the WHERE clause 'where'. What is
+// returned is guaranteed to be a valid WHERE clause (including a blank string).
+func AppendWhere(where, extra string) string {
+ if where == "" && extra == "" {
+ return ""
+ }
+ if where == "" {
+ where = BaseWhere + " "
+ } else {
+ where += " AND "
+ }
+ return where + extra
+}
+
+// GetRoleIDFromName returns the ID of the role associated with the supplied name
+func GetRoleIDFromName(tx *sql.Tx, roleName string) (int, error) {
+ var id int
+ if err := tx.QueryRow(`SELECT id FROM role WHERE name = $1`, roleName).Scan(&id); err != nil {
+ return -1, errors.New("querying role ID from name: " + err.Error())
+ }
+ return id, nil
+}
+
+// GetRoleNameFromID returns the name of the role associated with the supplied ID
Review comment:
same as the above comments on GetRoleIDFromName
##########
File path: traffic_ops/traffic_ops_golang/login/register.go
##########
@@ -164,6 +164,15 @@ func RegisterUser(w http.ResponseWriter, r *http.Request) {
api.HandleErr(w, r, tx, http.StatusBadRequest, userErr, nil)
return
}
+ // ToDo: uncomment this once the perm based roles and config options are implemented
+ //var reqV50 tc.UserRegistrationRequestV50
+ //if inf.Version.Major >= 5 {
+ // if userErr = api.Parse(r.Body, tx, &reqV50); userErr != nil {
+ // api.HandleErr(w, r, tx, http.StatusBadRequest, userErr, nil)
+ // return
+ // }
+ // tenantID = reqV50.TenantID
+ //}
Review comment:
Why can't we do this now? Or if you want to do it in another PR, why leave the comment in?
##########
File path: traffic_ops/traffic_ops_golang/role/roles.go
##########
@@ -273,3 +291,522 @@ RETURNING id, last_updated`
func deleteQuery() string {
return `DELETE FROM role WHERE id = :id`
}
+
+func Update(w http.ResponseWriter, r *http.Request) {
+ var roleID int
+ var roleName string
+ var roleDesc string
+ var privLevel int
+ var roleCapabilities *[]string
+ var roleV50 tc.RoleV50
+ var role tc.Role
+ var ok bool
+ var err error
+
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ version := inf.Version
+ if version == nil {
+ middleware.NotImplementedHandler().ServeHTTP(w, r)
+ return
+ }
+ tx := inf.Tx.Tx
+ if version.Major >= 5 {
+ if err := json.NewDecoder(r.Body).Decode(&roleV50); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+ if err := Validate(inf.Tx, role, roleV50, version); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+ roleDesc = *roleV50.Description
+ roleCapabilities = &roleV50.Permissions
+ if roleName, ok = inf.Params["name"]; !ok {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+ roleID, err = dbhelpers.GetRoleIDFromName(tx, roleName)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("no ID exists for the supplied role name"), nil)
+ return
+ }
+
+ existingLastUpdated, found, err := api.GetLastUpdated(inf.Tx, roleID, "role")
+ if err == nil && found == false {
+ api.HandleErr(w, r, tx, http.StatusNotFound, errors.New("no role found with that ID"), nil)
+ return
+ }
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err)
+ return
+ }
+
+ if !api.IsUnmodified(r.Header, *existingLastUpdated) {
+ api.HandleErr(w, r, tx, http.StatusPreconditionFailed, api.ResourceModifiedError, nil)
+ return
+ }
+ rows, err := tx.Query(updateRoleQuery(), roleDesc, roleName)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("updating role: "+err.Error()))
+ return
+ }
+ defer rows.Close()
+ if !rows.Next() {
+ api.HandleErr(w, r, tx, http.StatusNotFound, errors.New("no role found with this ID"), nil)
+ return
+ }
+ lastUpdated := tc.TimeNoMod{}
+ for rows.Next() {
+ if err := rows.Scan(&lastUpdated); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("scanning lastUpdated from role update: "+err.Error()))
+ return
+ }
+ roleV50.LastUpdated = &lastUpdated
+ }
+ }
+ //else {
+ // if err := json.NewDecoder(r.Body).Decode(&role); err != nil {
+ // api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ // return
+ // }
+ // if err := Validate(inf.Tx, role, roleV50, version); err != nil {
+ // api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("name and/or description and/or privLevel can not be empty"), nil)
+ // return
+ // }
+ // roleName = *role.Name
+ // roleDesc = *role.Description
+ // privLevel = *role.PrivLevel
+ // roleCapabilities = role.Capabilities
+ // if roleIDParam, ok := inf.Params["id"]; !ok {
+ // api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("must supply a role ID to update"), nil)
+ // return
+ // } else {
+ // roleID, err = strconv.Atoi(roleIDParam)
+ // if err != nil {
+ // api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("must supply an integral role ID to update"), nil)
+ // return
+ // }
+ // }
+ // if privLevel > inf.User.PrivLevel {
+ // api.HandleErr(w, r, tx, http.StatusForbidden, errors.New("can not create a role with a higher priv level than your own"), nil)
+ // return
+ // }
+ // existingLastUpdated, found, err := api.GetLastUpdated(inf.Tx, roleID, "role")
+ // if err == nil && found == false {
+ // api.HandleErr(w, r, tx, http.StatusNotFound, errors.New("no role found with that ID"), nil)
+ // return
+ // }
+ // if err != nil {
+ // api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err)
+ // return
+ // }
+ //
+ // if !api.IsUnmodified(r.Header, *existingLastUpdated) {
+ // api.HandleErr(w, r, tx, http.StatusPreconditionFailed, api.ResourceModifiedError, nil)
+ // return
+ // }
+ //
+ // rows, err := tx.Query(updateLegacyRoleQuery(), roleName, roleDesc, roleID)
+ // if err != nil {
+ // api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("updating role here: "+err.Error()))
+ // return
+ // }
+ // rows.Close()
+ //}
+
+ if roleCapabilities != nil && *roleCapabilities != nil {
+ userErr, sysErr, errCode = deleteRoleCapabilityAssociations(inf.Tx, roleID)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ userErr, sysErr, errCode = createRoleCapabilityAssociations(inf.Tx, roleID, roleCapabilities)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ }
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "role was updated.")
+ var roleResponse interface{}
+ if version.Major >= 5 {
+ var capabilities []string
+ if roleCapabilities != nil {
+ capabilities = *roleCapabilities
+ }
+ roleResponse = tc.RoleV50{
+ Name: util.StrPtr(roleName),
+ Permissions: capabilities,
+ Description: util.StrPtr(roleDesc),
+ }
+ } else {
+ roleResponse = tc.Role{
+ RoleV11: tc.RoleV11{
+ ID: util.IntPtr(roleID),
+ Name: util.StrPtr(roleName),
+ Description: util.StrPtr(roleDesc),
+ PrivLevel: util.IntPtr(privLevel),
+ },
+ Capabilities: roleCapabilities,
+ }
+ }
+ api.WriteAlertsObj(w, r, http.StatusOK, alerts, roleResponse)
+}
+
+func deleteRoleQuery() string {
+ return `DELETE FROM role WHERE id = $1`
+}
+
+func readQuery() string {
+ return `SELECT
+id,
+name,
+description,
+priv_level,
+last_updated,
+ARRAY(SELECT rc.cap_name FROM role_capability AS rc WHERE rc.role_id=id) AS permissions
+FROM role`
+}
+
+func createQuery() string {
+ return `INSERT INTO role (
+name,
+description,
+priv_level
+) VALUES (
+$1,
+$2,
+$3
+)
+RETURNING id, last_updated`
+}
+
+func createRoleCapabilityAssociations(tx *sqlx.Tx, roleID int, permissions *[]string) (error, error, int) {
+ result, err := tx.Exec(associateCapabilities(), roleID, pq.Array(permissions))
+ if err != nil {
+ return nil, errors.New("creating role capabilities: " + err.Error()), http.StatusInternalServerError
+ }
+
+ if rows, err := result.RowsAffected(); err != nil {
+ log.Errorf("could not check result after inserting role_capability relations: %v", err)
+ } else if expected := len(*permissions); int(rows) != expected {
+ log.Errorf("wrong number of role_capability rows created: %d expected: %d", rows, expected)
+ }
+ return nil, nil, http.StatusOK
+}
+
+func Validate(tx *sqlx.Tx, role tc.Role, roleV50 tc.RoleV50, version *api.Version) error {
+ var capabilities *[]string
+ errs := make(map[string]error)
+ if version.Major >= 5 {
+ errs = validation.Errors{
+ "name": validation.Validate(roleV50.Name, validation.Required),
+ "description": validation.Validate(roleV50.Description, validation.Required),
+ }
+ capabilities = &roleV50.Permissions
+ } else {
+ errs = validation.Errors{
+ "name": validation.Validate(role.Name, validation.Required),
+ "description": validation.Validate(role.Description, validation.Required),
+ "privLevel": validation.Validate(role.PrivLevel, validation.Required),
+ }
+ capabilities = role.Capabilities
+ }
+
+ 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)))`
Review comment:
We shouldn't do this, the source of truth for what Permissions exist is just the API documentation itself, not the database. That's why I got rid of the foreign key constraint, which enforced this at a database level.
##########
File path: lib/go-tc/users.go
##########
@@ -105,6 +348,33 @@ type UserCurrentV40 struct {
LastAuthenticated *time.Time `json:"lastAuthenticated" db:"last_authenticated"`
}
+// UserCurrentV50 represents the structure for the "current" user, and has the "Role" field as a *string, as opposed to a *int found in the older versions
+type UserCurrentV50 struct {
+ UserName *string `json:"username"`
+ LocalUser *bool `json:"localUser"`
+ AddressLine1 *string `json:"addressLine1"`
+ AddressLine2 *string `json:"addressLine2"`
+ City *string `json:"city"`
+ Company *string `json:"company"`
+ Country *string `json:"country"`
+ Email *string `json:"email"`
+ FullName *string `json:"fullName"`
+ GID *int `json:"gid"`
+ ID *int `json:"id"`
+ NewUser *bool `json:"newUser"`
+ PhoneNumber *string `json:"phoneNumber"`
+ PostalCode *string `json:"postalCode"`
+ PublicSSHKey *string `json:"publicSshKey"`
+ Role *string `json:"role"`
+ StateOrProvince *string `json:"stateOrProvince"`
+ Tenant *string `json:"tenant"`
+ TenantID *int `json:"tenantId"`
+ Token *string `json:"-"`
+ UID *int `json:"uid"`
+ LastUpdated *time.Time `json:"lastUpdated"`
+ LastAuthenticated *time.Time `json:"lastAuthenticated"`
Review comment:
Also same as above RE: V5 alias
##########
File path: traffic_ops/traffic_ops_golang/role/roles.go
##########
@@ -273,3 +291,522 @@ RETURNING id, last_updated`
func deleteQuery() string {
return `DELETE FROM role WHERE id = :id`
}
+
+func Update(w http.ResponseWriter, r *http.Request) {
+ var roleID int
+ var roleName string
+ var roleDesc string
+ var privLevel int
+ var roleCapabilities *[]string
+ var roleV50 tc.RoleV50
+ var role tc.Role
+ var ok bool
+ var err error
+
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ version := inf.Version
+ if version == nil {
+ middleware.NotImplementedHandler().ServeHTTP(w, r)
+ return
+ }
+ tx := inf.Tx.Tx
+ if version.Major >= 5 {
+ if err := json.NewDecoder(r.Body).Decode(&roleV50); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+ if err := Validate(inf.Tx, role, roleV50, version); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+ roleDesc = *roleV50.Description
+ roleCapabilities = &roleV50.Permissions
+ if roleName, ok = inf.Params["name"]; !ok {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+ roleID, err = dbhelpers.GetRoleIDFromName(tx, roleName)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("no ID exists for the supplied role name"), nil)
+ return
+ }
+
+ existingLastUpdated, found, err := api.GetLastUpdated(inf.Tx, roleID, "role")
+ if err == nil && found == false {
+ api.HandleErr(w, r, tx, http.StatusNotFound, errors.New("no role found with that ID"), nil)
+ return
+ }
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err)
+ return
+ }
+
+ if !api.IsUnmodified(r.Header, *existingLastUpdated) {
+ api.HandleErr(w, r, tx, http.StatusPreconditionFailed, api.ResourceModifiedError, nil)
+ return
+ }
+ rows, err := tx.Query(updateRoleQuery(), roleDesc, roleName)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("updating role: "+err.Error()))
+ return
+ }
+ defer rows.Close()
+ if !rows.Next() {
+ api.HandleErr(w, r, tx, http.StatusNotFound, errors.New("no role found with this ID"), nil)
+ return
+ }
+ lastUpdated := tc.TimeNoMod{}
+ for rows.Next() {
+ if err := rows.Scan(&lastUpdated); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("scanning lastUpdated from role update: "+err.Error()))
+ return
+ }
+ roleV50.LastUpdated = &lastUpdated
+ }
+ }
+ //else {
+ // if err := json.NewDecoder(r.Body).Decode(&role); err != nil {
+ // api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ // return
+ // }
+ // if err := Validate(inf.Tx, role, roleV50, version); err != nil {
+ // api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("name and/or description and/or privLevel can not be empty"), nil)
+ // return
+ // }
+ // roleName = *role.Name
+ // roleDesc = *role.Description
+ // privLevel = *role.PrivLevel
+ // roleCapabilities = role.Capabilities
+ // if roleIDParam, ok := inf.Params["id"]; !ok {
+ // api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("must supply a role ID to update"), nil)
+ // return
+ // } else {
+ // roleID, err = strconv.Atoi(roleIDParam)
+ // if err != nil {
+ // api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("must supply an integral role ID to update"), nil)
+ // return
+ // }
+ // }
+ // if privLevel > inf.User.PrivLevel {
+ // api.HandleErr(w, r, tx, http.StatusForbidden, errors.New("can not create a role with a higher priv level than your own"), nil)
+ // return
+ // }
+ // existingLastUpdated, found, err := api.GetLastUpdated(inf.Tx, roleID, "role")
+ // if err == nil && found == false {
+ // api.HandleErr(w, r, tx, http.StatusNotFound, errors.New("no role found with that ID"), nil)
+ // return
+ // }
+ // if err != nil {
+ // api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err)
+ // return
+ // }
+ //
+ // if !api.IsUnmodified(r.Header, *existingLastUpdated) {
+ // api.HandleErr(w, r, tx, http.StatusPreconditionFailed, api.ResourceModifiedError, nil)
+ // return
+ // }
+ //
+ // rows, err := tx.Query(updateLegacyRoleQuery(), roleName, roleDesc, roleID)
+ // if err != nil {
+ // api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("updating role here: "+err.Error()))
+ // return
+ // }
+ // rows.Close()
+ //}
Review comment:
doesn't this being commented-out break all PUT requests to `/roles` in API versions earlier than 5.0?
##########
File path: traffic_ops/traffic_ops_golang/user/user.go
##########
@@ -508,3 +519,426 @@ func (user *TOUser) InsertQuery() string {
func (user *TOUser) DeleteQuery() string {
return `DELETE FROM tm_user WHERE id = :id`
}
+
+const readBaseQuery = `
+SELECT
+ u.id,
+ u.username AS username,
+ u.public_ssh_key,
+ u.company,
+ u.email,
+ u.full_name,
+ u.new_user,
+ u.address_line1,
+ u.address_line2,
+ u.city,
+ u.state_or_province,
+ u.phone_number,
+ u.postal_code,
+ u.country,
+ u.registration_sent,
+ u.tenant_id,
+ t.name AS tenant,
+ u.last_updated,`
+
+const readQuery = readBaseQuery + `
+ r.name as role
+FROM tm_user u
+LEFT JOIN tenant t ON u.tenant_id = t.id
+LEFT JOIN role r ON u.role = r.id
+`
+
+const legacyReadQuery = readBaseQuery + `
+ r.name AS rolename,
+ u.role
+FROM tm_user u
+LEFT JOIN tenant t ON u.tenant_id = t.id
+LEFT JOIN role r ON u.role = r.id
+`
+
+// this is necessary because tc.User doesn't read its RoleName field in sql
+// driver scans.
+type userGet struct {
+ RoleName *string `json:"rolename" db:"rolename"`
+ tc.User
+}
+
+func read(rows *sqlx.Rows) ([]tc.UserV50, error) {
+ if rows == nil {
+ return nil, errors.New("cannot read from nil rows")
+ }
+
+ users := []tc.UserV50{}
+ for rows.Next() {
+ var user tc.UserV50
+ if err := rows.StructScan(&user); err != nil {
+ return nil, fmt.Errorf("scanning UserV5 row: %w", err)
+ }
+ users = append(users, user)
+ }
+
+ return users, nil
+}
+
+func getMaxLastUpdated(where string, queryValues map[string]interface{}, tx *sqlx.Tx) (time.Time, error) {
+ query := selectMaxLastUpdatedQuery(where)
+ var t time.Time
+ rows, err := tx.NamedQuery(query, queryValues)
+ if err != nil {
+ return t, fmt.Errorf("query for max user last updated time: %w", err)
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ if err = rows.Scan(&t); err != nil {
+ return t, fmt.Errorf("scanning user max last updated time: %w", err)
+ }
+ }
+ return t, nil
+}
+
+// Get is the handler for GET requests made to /users.
+func Get(w http.ResponseWriter, r *http.Request) {
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+ tx := inf.Tx.Tx
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ api.DefaultSort(inf, "username")
+ params := map[string]dbhelpers.WhereColumnInfo{
+ "id": {Column: "u.id", Checker: api.IsInt},
+ "role": {Column: "r.name"},
+ "tenant": {Column: "t.name"},
+ "username": {Column: "u.username"},
+ }
+ params["company"] = dbhelpers.WhereColumnInfo{Column: "u.company"}
+ params["email"] = dbhelpers.WhereColumnInfo{Column: "u.email"}
+ params["fullName"] = dbhelpers.WhereColumnInfo{Column: "u.full_name"}
+ params["newUser"] = dbhelpers.WhereColumnInfo{Column: "u.new_user"}
+ params["city"] = dbhelpers.WhereColumnInfo{Column: "u.city"}
+ params["stateOrProvince"] = dbhelpers.WhereColumnInfo{Column: "u.state_or_province"}
+ params["country"] = dbhelpers.WhereColumnInfo{Column: "u.country"}
+ params["postalCode"] = dbhelpers.WhereColumnInfo{Column: "u.postal_code"}
+
+ where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, params)
+ if len(errs) != 0 {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, util.JoinErrs(errs), nil)
+ return
+ }
+
+ tenantIDs, err := tenant.GetUserTenantIDListTx(inf.Tx.Tx, inf.User.TenantID)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("getting tenant list for user: %v\n", err))
Review comment:
Errors should not contain newlines, also formatting for errors should use `%w` instead of `%v` so that it wraps
##########
File path: traffic_ops/traffic_ops_golang/role/roles.go
##########
@@ -273,3 +291,522 @@ RETURNING id, last_updated`
func deleteQuery() string {
return `DELETE FROM role WHERE id = :id`
}
+
+func Update(w http.ResponseWriter, r *http.Request) {
+ var roleID int
+ var roleName string
+ var roleDesc string
+ var privLevel int
+ var roleCapabilities *[]string
+ var roleV50 tc.RoleV50
+ var role tc.Role
+ var ok bool
+ var err error
+
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ version := inf.Version
+ if version == nil {
+ middleware.NotImplementedHandler().ServeHTTP(w, r)
+ return
+ }
+ tx := inf.Tx.Tx
+ if version.Major >= 5 {
+ if err := json.NewDecoder(r.Body).Decode(&roleV50); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+ if err := Validate(inf.Tx, role, roleV50, version); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+ roleDesc = *roleV50.Description
+ roleCapabilities = &roleV50.Permissions
+ if roleName, ok = inf.Params["name"]; !ok {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+ roleID, err = dbhelpers.GetRoleIDFromName(tx, roleName)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("no ID exists for the supplied role name"), nil)
+ return
+ }
+
+ existingLastUpdated, found, err := api.GetLastUpdated(inf.Tx, roleID, "role")
+ if err == nil && found == false {
+ api.HandleErr(w, r, tx, http.StatusNotFound, errors.New("no role found with that ID"), nil)
+ return
+ }
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err)
+ return
+ }
+
+ if !api.IsUnmodified(r.Header, *existingLastUpdated) {
+ api.HandleErr(w, r, tx, http.StatusPreconditionFailed, api.ResourceModifiedError, nil)
+ return
+ }
+ rows, err := tx.Query(updateRoleQuery(), roleDesc, roleName)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("updating role: "+err.Error()))
+ return
+ }
+ defer rows.Close()
+ if !rows.Next() {
+ api.HandleErr(w, r, tx, http.StatusNotFound, errors.New("no role found with this ID"), nil)
+ return
+ }
+ lastUpdated := tc.TimeNoMod{}
+ for rows.Next() {
+ if err := rows.Scan(&lastUpdated); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("scanning lastUpdated from role update: "+err.Error()))
+ return
+ }
+ roleV50.LastUpdated = &lastUpdated
+ }
+ }
+ //else {
+ // if err := json.NewDecoder(r.Body).Decode(&role); err != nil {
+ // api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ // return
+ // }
+ // if err := Validate(inf.Tx, role, roleV50, version); err != nil {
+ // api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("name and/or description and/or privLevel can not be empty"), nil)
+ // return
+ // }
+ // roleName = *role.Name
+ // roleDesc = *role.Description
+ // privLevel = *role.PrivLevel
+ // roleCapabilities = role.Capabilities
+ // if roleIDParam, ok := inf.Params["id"]; !ok {
+ // api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("must supply a role ID to update"), nil)
+ // return
+ // } else {
+ // roleID, err = strconv.Atoi(roleIDParam)
+ // if err != nil {
+ // api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("must supply an integral role ID to update"), nil)
+ // return
+ // }
+ // }
+ // if privLevel > inf.User.PrivLevel {
+ // api.HandleErr(w, r, tx, http.StatusForbidden, errors.New("can not create a role with a higher priv level than your own"), nil)
+ // return
+ // }
+ // existingLastUpdated, found, err := api.GetLastUpdated(inf.Tx, roleID, "role")
+ // if err == nil && found == false {
+ // api.HandleErr(w, r, tx, http.StatusNotFound, errors.New("no role found with that ID"), nil)
+ // return
+ // }
+ // if err != nil {
+ // api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err)
+ // return
+ // }
+ //
+ // if !api.IsUnmodified(r.Header, *existingLastUpdated) {
+ // api.HandleErr(w, r, tx, http.StatusPreconditionFailed, api.ResourceModifiedError, nil)
+ // return
+ // }
+ //
+ // rows, err := tx.Query(updateLegacyRoleQuery(), roleName, roleDesc, roleID)
+ // if err != nil {
+ // api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("updating role here: "+err.Error()))
+ // return
+ // }
+ // rows.Close()
+ //}
+
+ if roleCapabilities != nil && *roleCapabilities != nil {
+ userErr, sysErr, errCode = deleteRoleCapabilityAssociations(inf.Tx, roleID)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ userErr, sysErr, errCode = createRoleCapabilityAssociations(inf.Tx, roleID, roleCapabilities)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ }
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "role was updated.")
+ var roleResponse interface{}
+ if version.Major >= 5 {
+ var capabilities []string
+ if roleCapabilities != nil {
+ capabilities = *roleCapabilities
+ }
+ roleResponse = tc.RoleV50{
+ Name: util.StrPtr(roleName),
+ Permissions: capabilities,
+ Description: util.StrPtr(roleDesc),
+ }
+ } else {
+ roleResponse = tc.Role{
+ RoleV11: tc.RoleV11{
+ ID: util.IntPtr(roleID),
+ Name: util.StrPtr(roleName),
+ Description: util.StrPtr(roleDesc),
+ PrivLevel: util.IntPtr(privLevel),
+ },
+ Capabilities: roleCapabilities,
+ }
+ }
+ api.WriteAlertsObj(w, r, http.StatusOK, alerts, roleResponse)
+}
+
+func deleteRoleQuery() string {
+ return `DELETE FROM role WHERE id = $1`
+}
+
+func readQuery() string {
+ return `SELECT
+id,
+name,
+description,
+priv_level,
+last_updated,
+ARRAY(SELECT rc.cap_name FROM role_capability AS rc WHERE rc.role_id=id) AS permissions
+FROM role`
+}
+
+func createQuery() string {
+ return `INSERT INTO role (
+name,
+description,
+priv_level
+) VALUES (
+$1,
+$2,
+$3
+)
+RETURNING id, last_updated`
+}
+
+func createRoleCapabilityAssociations(tx *sqlx.Tx, roleID int, permissions *[]string) (error, error, int) {
+ result, err := tx.Exec(associateCapabilities(), roleID, pq.Array(permissions))
+ if err != nil {
+ return nil, errors.New("creating role capabilities: " + err.Error()), http.StatusInternalServerError
+ }
+
+ if rows, err := result.RowsAffected(); err != nil {
+ log.Errorf("could not check result after inserting role_capability relations: %v", err)
+ } else if expected := len(*permissions); int(rows) != expected {
+ log.Errorf("wrong number of role_capability rows created: %d expected: %d", rows, expected)
+ }
+ return nil, nil, http.StatusOK
+}
+
+func Validate(tx *sqlx.Tx, role tc.Role, roleV50 tc.RoleV50, version *api.Version) error {
+ var capabilities *[]string
+ errs := make(map[string]error)
+ if version.Major >= 5 {
+ errs = validation.Errors{
+ "name": validation.Validate(roleV50.Name, validation.Required),
+ "description": validation.Validate(roleV50.Description, validation.Required),
+ }
+ capabilities = &roleV50.Permissions
+ } else {
+ errs = validation.Errors{
+ "name": validation.Validate(role.Name, validation.Required),
+ "description": validation.Validate(role.Description, validation.Required),
+ "privLevel": validation.Validate(role.PrivLevel, validation.Required),
+ }
+ capabilities = role.Capabilities
+ }
+
+ 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
+ if tx != nil {
+ err := tx.Select(&badCaps, checkCaps, pq.Array(capabilities))
+ if err != nil {
+ log.Errorf("got error from selecting bad capabilities: %v", err)
+ return err
+ }
+ if len(badCaps) > 0 {
+ errsToReturn = append(errsToReturn, fmt.Errorf("can not add non-existent capabilities: %v", badCaps))
+ }
+ }
+ return util.JoinErrs(errsToReturn)
+}
+
+func Delete(w http.ResponseWriter, r *http.Request) {
+ var roleName string
+ var ok bool
+ var roleID int
+ var err error
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ tx := inf.Tx.Tx
+ version := inf.Version
+ if version == nil {
+ middleware.NotImplementedHandler().ServeHTTP(w, r)
+ return
+ }
+
+ if version.Major >= 5 {
+ if roleName, ok = inf.Params["name"]; !ok {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("must supply a role name to delete"), nil)
+ return
+ }
+ roleID, err = dbhelpers.GetRoleIDFromName(tx, roleName)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("no ID exists for the supplied role name"), nil)
+ return
+ }
+ } else {
+ if roleIDParam, ok := inf.Params["id"]; !ok {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("must supply a role ID to delete"), nil)
+ return
+ } else {
+ roleID, err = strconv.Atoi(roleIDParam)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("must supply an integral role ID to delete"), nil)
+ return
+ }
+ }
+ }
+
+ assignedUsers := 0
+ if err := inf.Tx.Get(&assignedUsers, "SELECT COUNT(id) FROM tm_user WHERE role=$1", roleID); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("role delete counting assigned users: "+err.Error()))
+ return
+ } else if assignedUsers != 0 {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("can not delete a role with %d assigned users", assignedUsers), nil)
+ return
+ }
+
+ rows, err := tx.Query(deleteRoleQuery(), roleID)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("deleting role: "+err.Error()))
+ return
+ }
+ rows.Close()
+ userErr, sysErr, errCode = deleteRoleCapabilityAssociations(inf.Tx, roleID)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "role was deleted.")
+ api.WriteAlerts(w, r, http.StatusOK, alerts)
+}
+
+func deleteRoleCapabilityAssociations(tx *sqlx.Tx, roleID int) (error, error, int) {
+ result, err := tx.Exec(deleteAssociatedCapabilities(), roleID)
+ if err != nil {
+ return nil, errors.New("deleting role capabilities: " + err.Error()), http.StatusInternalServerError
+ }
+ if _, err = result.RowsAffected(); err != nil {
+ log.Errorf("could not check result after inserting role_capability relations: %v", err)
+ }
+ // TODO verify expected row count shouldn't be checked?
+ return nil, nil, http.StatusOK
+}
+
+func Create(w http.ResponseWriter, r *http.Request) {
+ var roleID int
+ var roleName string
+ var roleDesc string
+ var privLevel int
+ var roleCapabilities *[]string
+ var roleV50 tc.RoleV50
+ var role tc.Role
+
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ tx := inf.Tx.Tx
+ version := inf.Version
+ if version == nil {
+ middleware.NotImplementedHandler().ServeHTTP(w, r)
+ return
+ }
+
+ if version.Major >= 5 {
+ if err := json.NewDecoder(r.Body).Decode(&roleV50); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+ if err := Validate(inf.Tx, role, roleV50, version); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+ roleName = *roleV50.Name
+ roleDesc = *roleV50.Description
+ privLevel = inf.User.PrivLevel
+ roleCapabilities = &roleV50.Permissions
+ } else {
+ if err := json.NewDecoder(r.Body).Decode(&role); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+ if err := Validate(inf.Tx, role, roleV50, version); err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+ return
+ }
+ roleName = *role.Name
+ roleDesc = *role.Description
+ privLevel = *role.PrivLevel
+ roleCapabilities = role.Capabilities
+ }
+
+ rows, err := tx.Query(createQuery(), roleName, roleDesc, privLevel)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("creating role: "+err.Error()))
+ return
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var throwaway interface{}
+ if err := rows.Scan(&roleID, &throwaway); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("role create: scanning role ID: "+err.Error()))
+ return
+ }
+ }
+
+ if roleCapabilities != nil && len(*roleCapabilities) > 0 {
+ userErr, sysErr, errCode = createRoleCapabilityAssociations(inf.Tx, roleID, roleCapabilities)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ }
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "role was created.")
+ var roleResponse interface{}
+ if version.Major >= 5 {
+ var capabilities []string
+ if roleCapabilities != nil {
+ capabilities = *roleCapabilities
+ }
+ roleResponse = tc.RoleV50{
+ Name: util.StrPtr(roleName),
+ Permissions: capabilities,
+ Description: util.StrPtr(roleDesc),
+ }
+ } else {
+ roleResponse = tc.Role{
+ RoleV11: tc.RoleV11{
+ ID: util.IntPtr(roleID),
+ Name: util.StrPtr(roleName),
+ Description: util.StrPtr(roleDesc),
+ PrivLevel: util.IntPtr(privLevel),
+ },
+ Capabilities: roleCapabilities,
+ }
+ }
+ api.WriteAlertsObj(w, r, http.StatusOK, alerts, roleResponse)
+}
+
+func Get(w http.ResponseWriter, r *http.Request) {
+ var maxTime time.Time
+ var runSecond bool
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ tx := inf.Tx.Tx
+ version := inf.Version
+ if version == nil {
+ middleware.NotImplementedHandler().ServeHTTP(w, r)
+ return
+ }
+
+ params := make(map[string]dbhelpers.WhereColumnInfo, 0)
+ if version.Major >= 5 {
+ params["name"] = dbhelpers.WhereColumnInfo{Column: "name"}
+ } else {
+ params["name"] = dbhelpers.WhereColumnInfo{Column: "name"}
+ params["id"] = dbhelpers.WhereColumnInfo{Column: "id", Checker: api.IsInt}
+ params["privLevel"] = dbhelpers.WhereColumnInfo{Column: "priv_level", Checker: api.IsInt}
+ }
+
+ if _, ok := inf.Params["orderby"]; !ok {
+ inf.Params["orderby"] = "name"
+ }
+ where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, params)
+ if len(errs) != 0 {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, util.JoinErrs(errs), nil)
+ return
+ }
+ if version.Major >= 5 {
+ if perm, ok := inf.Params["can"]; ok {
+ queryValues["can"] = perm
+ where = dbhelpers.AppendWhere(where, "permissions @> :can")
+ }
+ }
+ if inf.Config.UseIMS {
+ runSecond, maxTime = ims.TryIfModifiedSinceQuery(inf.Tx, r.Header, queryValues, selectMaxLastUpdatedQuery(where))
+ if !runSecond {
+ log.Debugln("IMS HIT")
+ api.AddLastModifiedHdr(w, maxTime)
+ w.WriteHeader(http.StatusNotModified)
+ api.WriteResp(w, r, []interface{}{})
Review comment:
Is this `api.WriteResp` call actually necessary? I see other IMS hits doing it, but idk why, since the whole point is that clients will just ignore it and use their cache.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@trafficcontrol.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org