This is an automated email from the ASF dual-hosted git repository.
ocket8888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git
The following commit(s) were added to refs/heads/master by this push:
new bba3dc4 Locking down snap/ queue/ update status endpoints for a CDN
(#5930)
bba3dc4 is described below
commit bba3dc43ad5d3b0bd26394e5ac9abcce273cdcc0
Author: Srijeet Chatterjee <[email protected]>
AuthorDate: Wed Jun 23 14:55:15 2021 -0600
Locking down snap/ queue/ update status endpoints for a CDN (#5930)
* Initial changes for locking snap/ queue/ update status endpoints
* Adding tests
* changing lock creation message to indicate if it was a soft/ hard lock
* code review
* adding punctuation for godoc
* fix tests
---
blueprints/to-locks.md | 2 +-
docs/source/api/v4/cdn_locks.rst | 2 +-
traffic_ops/testing/api/v4/cdn_locks_test.go | 288 ++++++++++++++++++++-
.../traffic_ops_golang/cachegroup/queueupdate.go | 5 +
traffic_ops/traffic_ops_golang/cdn/queue.go | 14 +-
.../traffic_ops_golang/cdn_lock/cdn_lock.go | 8 +-
traffic_ops/traffic_ops_golang/crconfig/handler.go | 6 +-
.../traffic_ops_golang/dbhelpers/db_helpers.go | 34 +++
.../traffic_ops_golang/server/put_status.go | 11 +-
.../traffic_ops_golang/server/queue_update.go | 11 +
traffic_ops/traffic_ops_golang/server/update.go | 23 +-
.../traffic_ops_golang/topology/queue_update.go | 15 ++
12 files changed, 406 insertions(+), 13 deletions(-)
diff --git a/blueprints/to-locks.md b/blueprints/to-locks.md
index e7020bd..6eb421a 100644
--- a/blueprints/to-locks.md
+++ b/blueprints/to-locks.md
@@ -168,7 +168,7 @@ response JSON:
{
"alerts": [
{
- "text": "CDN lock acquired!",
+ "text": "hard CDN lock acquired!",
"level": "success"
}
],
diff --git a/docs/source/api/v4/cdn_locks.rst b/docs/source/api/v4/cdn_locks.rst
index 333c2df..198c562 100644
--- a/docs/source/api/v4/cdn_locks.rst
+++ b/docs/source/api/v4/cdn_locks.rst
@@ -122,7 +122,7 @@ Response Structure
{ "alerts": [
{
- "text": "CDN lock acquired!",
+ "text": "soft CDN lock acquired!",
"level":"success"
}
],
diff --git a/traffic_ops/testing/api/v4/cdn_locks_test.go
b/traffic_ops/testing/api/v4/cdn_locks_test.go
index bb3ebb6..c8366f8 100644
--- a/traffic_ops/testing/api/v4/cdn_locks_test.go
+++ b/traffic_ops/testing/api/v4/cdn_locks_test.go
@@ -19,6 +19,7 @@ import (
"fmt"
"net/http"
"net/url"
+ "strconv"
"testing"
"time"
@@ -28,9 +29,12 @@ import (
)
func TestCDNLocks(t *testing.T) {
- WithObjs(t, []TCObj{Tenants, Roles, Users, CDNs}, func() {
+ WithObjs(t, []TCObj{Types, CacheGroups, CDNs, Parameters, Profiles,
Statuses, Divisions, Regions, PhysLocations, Servers, ServerCapabilities,
ServerServerCapabilitiesForTopologies, Topologies, Tenants, DeliveryServices,
TopologyBasedDeliveryServiceRequiredCapabilities, Roles, Users}, func() {
CRDCdnLocks(t)
AdminCdnLocks(t)
+ SnapshotWithLock(t)
+ QueueUpdatesWithLock(t)
+ QueueUpdatesFromTopologiesWithLock(t)
})
}
@@ -45,6 +49,64 @@ func getCDNName(t *testing.T) string {
return cdnResp.Response[0].Name
}
+func getCDNNameAndServerID(t *testing.T) (string, int) {
+ serverID := -1
+ cdnResp, _, err := TOSession.GetCDNs(client.RequestOptions{})
+ if err != nil {
+ t.Fatalf("couldn't get CDNs: %v", err)
+ }
+ if len(cdnResp.Response) < 1 {
+ t.Fatalf("no valid CDNs in response")
+ }
+ for _, cdn := range cdnResp.Response {
+ opts := client.NewRequestOptions()
+ opts.QueryParameters.Set("cdn", strconv.Itoa(cdn.ID))
+ serversResp, _, err := TOSession.GetServers(opts)
+ if err != nil {
+ t.Errorf("could not get servers for cdn %s: %v",
cdn.Name, err)
+ }
+ if len(serversResp.Response) != 0 && serversResp.Response[0].ID
!= nil {
+ serverID = *serversResp.Response[0].ID
+ return cdn.Name, serverID
+ }
+ }
+ return "", serverID
+}
+
+func getCDNDetailsAndTopologyName(t *testing.T) (int, string, string) {
+ opts := client.NewRequestOptions()
+ topologiesResp, _, err :=
TOSession.GetTopologies(client.RequestOptions{})
+ if err != nil {
+ t.Fatalf("couldn't get topologies, err: %v", err)
+ }
+ if len(topologiesResp.Response) == 0 {
+ t.Fatal("no topologies returned")
+ }
+ for _, top := range topologiesResp.Response {
+ for _, node := range top.Nodes {
+ opts.QueryParameters.Set("name", node.Cachegroup)
+ cacheGroupResp, _, err := TOSession.GetCacheGroups(opts)
+ if err != nil {
+ t.Errorf("error while GETting cachegroups: %v",
err)
+ }
+ if len(cacheGroupResp.Response) != 0 &&
cacheGroupResp.Response[0].ID != nil {
+ cacheGroupID := *cacheGroupResp.Response[0].ID
+ opts.QueryParameters.Del("name")
+ opts.QueryParameters.Set("cachegroup",
strconv.Itoa(cacheGroupID))
+ serversResp, _, err :=
TOSession.GetServers(opts)
+ if err != nil {
+ t.Errorf("couldn't get servers: %v",
err)
+ }
+ if len(serversResp.Response) != 0 &&
serversResp.Response[0].CDNName != nil && serversResp.Response[0].CDNID != nil {
+ return *serversResp.Response[0].CDNID,
*serversResp.Response[0].CDNName, top.Name
+ }
+ }
+ }
+ }
+ t.Error("couldn't find a valid CDN and associated Topology name")
+ return -1, "", ""
+}
+
func CRDCdnLocks(t *testing.T) {
cdn := getCDNName(t)
// CREATE
@@ -199,3 +261,227 @@ func AdminCdnLocks(t *testing.T) {
t.Fatalf("expected a 200 status code, but got %d instead",
reqInf.StatusCode)
}
}
+
+func SnapshotWithLock(t *testing.T) {
+ resp, _, err := TOSession.GetTenants(client.RequestOptions{})
+ if err != nil {
+ t.Fatalf("could not GET tenants: %v", err)
+ }
+ if len(resp.Response) == 0 {
+ t.Fatalf("didn't get any tenant in response")
+ }
+
+ // Create a new user with operations level privileges
+ user1 := tc.User{
+ Username: util.StrPtr("lock_user1"),
+ RegistrationSent: tc.TimeNoModFromTime(time.Now()),
+ LocalPassword: util.StrPtr("test_pa$$word"),
+ ConfirmLocalPassword: util.StrPtr("test_pa$$word"),
+ RoleName: util.StrPtr("operations"),
+ }
+ user1.Email = util.StrPtr("[email protected]")
+ user1.TenantID = util.IntPtr(resp.Response[0].ID)
+ user1.FullName = util.StrPtr("firstName LastName")
+ _, _, err = TOSession.CreateUser(user1, client.RequestOptions{})
+ if err != nil {
+ t.Fatalf("could not create test user with username: %s",
*user1.Username)
+ }
+ defer ForceDeleteTestUsersByUsernames(t, []string{"lock_user1"})
+
+ // Establish a session with the newly created non admin level user
+ userSession, _, err := client.LoginWithAgent(Config.TrafficOps.URL,
*user1.Username, *user1.LocalPassword, true, "to-api-v4-client-tests", false,
toReqTimeout)
+ if err != nil {
+ t.Fatalf("could not login with user lock_user1: %v", err)
+ }
+
+ cdn := getCDNName(t)
+
+ // Currently, no user has a lock on the "bar" CDN, so when
"lock_user1", which does not have the lock on CDN "bar", tries to snap it, this
should pass
+ opts := client.NewRequestOptions()
+ opts.QueryParameters.Set("cdn", cdn)
+ _, _, err = userSession.SnapshotCRConfig(opts)
+ if err != nil {
+ t.Errorf("expected no error while snapping cdn %s by user %s,
but got %v", cdn, *user1.Username, err)
+ }
+
+ // Create a lock for this user
+ _, _, err = userSession.CreateCDNLock(tc.CDNLock{
+ CDN: cdn,
+ Message: util.StrPtr("test lock"),
+ Soft: util.BoolPtr(true),
+ }, client.RequestOptions{})
+ if err != nil {
+ t.Fatalf("couldn't create cdn lock: %v", err)
+ }
+
+ // "lock_user1", which has the lock on CDN "bar", tries to snap it ->
this should pass
+ _, _, err = userSession.SnapshotCRConfig(opts)
+ if err != nil {
+ t.Errorf("expected no error while snapping cdn %s by user %s,
but got %v", cdn, *user1.Username, err)
+ }
+
+ // Admin user, which doesn't have the lock on the CDN "bar", is trying
to snap it -> this should fail
+ _, reqInf, err := TOSession.SnapshotCRConfig(opts)
+ if err == nil {
+ t.Errorf("expected error while snapping cdn %s by user admin,
but got nothing", cdn)
+ }
+ if reqInf.StatusCode != http.StatusForbidden {
+ t.Errorf("expected a 403 status code, but got %d instead",
reqInf.StatusCode)
+ }
+
+ // Delete the lock
+ _, _, err =
userSession.DeleteCDNLocks(client.RequestOptions{QueryParameters:
url.Values{"cdn": []string{cdn}}})
+ if err != nil {
+ t.Errorf("expected no error while deleting other user's lock
using admin endpoint, but got %v", err)
+ }
+}
+
+func QueueUpdatesWithLock(t *testing.T) {
+ resp, _, err := TOSession.GetTenants(client.RequestOptions{})
+ if err != nil {
+ t.Fatalf("could not GET tenants: %v", err)
+ }
+ if len(resp.Response) == 0 {
+ t.Fatalf("didn't get any tenant in response")
+ }
+
+ // Create a new user with operations level privileges
+ user1 := tc.User{
+ Username: util.StrPtr("lock_user1"),
+ RegistrationSent: tc.TimeNoModFromTime(time.Now()),
+ LocalPassword: util.StrPtr("test_pa$$word"),
+ ConfirmLocalPassword: util.StrPtr("test_pa$$word"),
+ RoleName: util.StrPtr("operations"),
+ }
+ user1.Email = util.StrPtr("[email protected]")
+ user1.TenantID = util.IntPtr(resp.Response[0].ID)
+ user1.FullName = util.StrPtr("firstName LastName")
+ _, _, err = TOSession.CreateUser(user1, client.RequestOptions{})
+ if err != nil {
+ t.Fatalf("could not create test user with username: %s",
*user1.Username)
+ }
+ defer ForceDeleteTestUsersByUsernames(t, []string{"lock_user1"})
+
+ // Establish a session with the newly created non admin level user
+ userSession, _, err := client.LoginWithAgent(Config.TrafficOps.URL,
*user1.Username, *user1.LocalPassword, true, "to-api-v4-client-tests", false,
toReqTimeout)
+ if err != nil {
+ t.Fatalf("could not login with user lock_user1: %v", err)
+ }
+
+ cdn, serverID := getCDNNameAndServerID(t)
+ if serverID == -1 {
+ t.Fatalf("Could not get any valid servers to queue updates on")
+ }
+
+ // Currently, no user has a lock on the "bar" CDN, so when
"lock_user1", which does not have the lock on CDN "bar", tries to queue updates
on a server in the same CDN, this should pass
+ _, _, err = userSession.SetServerQueueUpdate(serverID, true,
client.RequestOptions{})
+ if err != nil {
+ t.Errorf("expected no error while queueing updates for a server
in cdn %s by user %s, but got %v", cdn, *user1.Username, err)
+ }
+
+ // Create a lock for this user
+ _, _, err = userSession.CreateCDNLock(tc.CDNLock{
+ CDN: cdn,
+ Message: util.StrPtr("test lock"),
+ Soft: util.BoolPtr(true),
+ }, client.RequestOptions{})
+ if err != nil {
+ t.Fatalf("couldn't create cdn lock: %v", err)
+ }
+
+ // "lock_user1", which has the lock on CDN "bar", tries to queue
updates on a server in it -> this should pass
+ _, _, err = userSession.SetServerQueueUpdate(serverID, true,
client.RequestOptions{})
+ if err != nil {
+ t.Errorf("expected no error while queueing updates for a server
in cdn %s by user %s, but got %v", cdn, *user1.Username, err)
+ }
+
+ // Admin user, which doesn't have the lock on the CDN "bar", is trying
to queue updates on a server in it -> this should fail
+ _, reqInf, err := TOSession.SetServerQueueUpdate(serverID, true,
client.RequestOptions{})
+ if err == nil {
+ t.Errorf("expected error while queueing updates on a server in
cdn %s by user admin, but got nothing", cdn)
+ }
+ if reqInf.StatusCode != http.StatusForbidden {
+ t.Fatalf("expected a 403 status code, but got %d instead",
reqInf.StatusCode)
+ }
+
+ // Delete the lock
+ _, _, err =
userSession.DeleteCDNLocks(client.RequestOptions{QueryParameters:
url.Values{"cdn": []string{cdn}}})
+ if err != nil {
+ t.Fatalf("expected no error while deleting other user's lock
using admin endpoint, but got %v", err)
+ }
+}
+
+func QueueUpdatesFromTopologiesWithLock(t *testing.T) {
+ resp, _, err := TOSession.GetTenants(client.RequestOptions{})
+ if err != nil {
+ t.Fatalf("could not GET tenants: %v", err)
+ }
+ if len(resp.Response) == 0 {
+ t.Fatalf("didn't get any tenant in response")
+ }
+
+ // Create a new user with operations level privileges
+ user1 := tc.User{
+ Username: util.StrPtr("lock_user1"),
+ RegistrationSent: tc.TimeNoModFromTime(time.Now()),
+ LocalPassword: util.StrPtr("test_pa$$word"),
+ ConfirmLocalPassword: util.StrPtr("test_pa$$word"),
+ RoleName: util.StrPtr("operations"),
+ }
+ user1.Email = util.StrPtr("[email protected]")
+ user1.TenantID = util.IntPtr(resp.Response[0].ID)
+ user1.FullName = util.StrPtr("firstName LastName")
+ _, _, err = TOSession.CreateUser(user1, client.RequestOptions{})
+ if err != nil {
+ t.Fatalf("could not create test user with username: %s",
*user1.Username)
+ }
+ defer ForceDeleteTestUsersByUsernames(t, []string{"lock_user1"})
+
+ // Establish a session with the newly created non admin level user
+ userSession, _, err := client.LoginWithAgent(Config.TrafficOps.URL,
*user1.Username, *user1.LocalPassword, true, "to-api-v4-client-tests", false,
toReqTimeout)
+ if err != nil {
+ t.Fatalf("could not login with user lock_user1: %v", err)
+ }
+
+ cdnID, cdnName, topology := getCDNDetailsAndTopologyName(t)
+ if topology == "" || cdnName == "" || cdnID == -1 {
+ t.Fatalf("Could not get any valid topologies/ cdns to queue
updates on")
+ }
+
+ // Currently, no user has a lock on the "bar" CDN, so when
"lock_user1", which does not have the lock on CDN "bar", tries to queue updates
on a topology in the same CDN, this should pass
+ _, _, err = userSession.TopologiesQueueUpdate(topology,
tc.TopologiesQueueUpdateRequest{Action: "queue", CDNID: int64(cdnID)},
client.RequestOptions{})
+ if err != nil {
+ t.Errorf("expected no error while queueing updates for a
topology server in cdn %s by user %s, but got %v", cdnName, *user1.Username,
err)
+ }
+
+ // Create a lock for this user
+ _, _, err = userSession.CreateCDNLock(tc.CDNLock{
+ CDN: cdnName,
+ Message: util.StrPtr("test lock"),
+ Soft: util.BoolPtr(true),
+ }, client.RequestOptions{})
+ if err != nil {
+ t.Fatalf("couldn't create cdn lock: %v", err)
+ }
+
+ // "lock_user1", which has the lock on CDN "bar", tries to queue
updates on a topology in it -> this should pass
+ _, _, err = userSession.TopologiesQueueUpdate(topology,
tc.TopologiesQueueUpdateRequest{Action: "queue", CDNID: int64(cdnID)},
client.RequestOptions{})
+ if err != nil {
+ t.Errorf("expected no error while queueing updates for a
topology server in cdn %s by user %s, but got %v", cdnName, *user1.Username,
err)
+ }
+
+ // Admin user, which doesn't have the lock on the CDN "bar", is trying
to queue updates on a topology in it -> this should fail
+ _, reqInf, err := TOSession.TopologiesQueueUpdate(topology,
tc.TopologiesQueueUpdateRequest{Action: "queue", CDNID: int64(cdnID)},
client.RequestOptions{})
+ if err == nil {
+ t.Errorf("expected error while queueing updates on topology
servers on cdn %s by user admin, but got nothing", cdnName)
+ }
+ if reqInf.StatusCode != http.StatusForbidden {
+ t.Fatalf("expected a 403 status code, but got %d instead",
reqInf.StatusCode)
+ }
+
+ // Delete the lock
+ _, _, err =
userSession.DeleteCDNLocks(client.RequestOptions{QueryParameters:
url.Values{"cdn": []string{cdnName}}})
+ if err != nil {
+ t.Fatalf("expected no error while deleting lock, but got %v",
err)
+ }
+}
diff --git a/traffic_ops/traffic_ops_golang/cachegroup/queueupdate.go
b/traffic_ops/traffic_ops_golang/cachegroup/queueupdate.go
index b0ffc39..8527185 100644
--- a/traffic_ops/traffic_ops_golang/cachegroup/queueupdate.go
+++ b/traffic_ops/traffic_ops_golang/cachegroup/queueupdate.go
@@ -74,6 +74,11 @@ func QueueUpdates(w http.ResponseWriter, r *http.Request) {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, nil, nil)
return
}
+ userErr, sysErr, statusCode :=
dbhelpers.CheckIfCurrentUserHasCdnLock(inf.Tx.Tx, string(*reqObj.CDN),
inf.User.UserName)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr)
+ return
+ }
queue := reqObj.Action == "queue"
updatedCaches, err := queueUpdates(inf.Tx.Tx, cgID, *reqObj.CDN, queue)
if err != nil {
diff --git a/traffic_ops/traffic_ops_golang/cdn/queue.go
b/traffic_ops/traffic_ops_golang/cdn/queue.go
index fe2f402..05e175d 100644
--- a/traffic_ops/traffic_ops_golang/cdn/queue.go
+++ b/traffic_ops/traffic_ops_golang/cdn/queue.go
@@ -48,11 +48,6 @@ func Queue(w http.ResponseWriter, r *http.Request) {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest,
errors.New("action must be 'queue' or 'dequeue'"), nil)
return
}
- if err := queueUpdates(inf.Tx.Tx, int64(inf.IntParams["id"]),
reqObj.Action == "queue"); err != nil {
- api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, errors.New("CDN queueing updates: "+err.Error()))
- return
- }
-
cdnName, ok, err := dbhelpers.GetCDNNameFromID(inf.Tx.Tx,
int64(inf.IntParams["id"]))
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, errors.New("getting cdn name from ID '"+inf.Params["id"]+"':
"+err.Error()))
@@ -61,6 +56,15 @@ func Queue(w http.ResponseWriter, r *http.Request) {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, nil, nil)
return
}
+ userErr, sysErr, statusCode :=
dbhelpers.CheckIfCurrentUserHasCdnLock(inf.Tx.Tx, string(cdnName),
inf.User.UserName)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr)
+ return
+ }
+ if err := queueUpdates(inf.Tx.Tx, int64(inf.IntParams["id"]),
reqObj.Action == "queue"); err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, errors.New("CDN queueing updates: "+err.Error()))
+ return
+ }
api.CreateChangeLogRawTx(api.ApiChange, "CDN: "+string(cdnName)+", ID:
"+strconv.Itoa(inf.IntParams["id"])+", ACTION: CDN server updates
"+reqObj.Action+"d", inf.User, inf.Tx.Tx)
api.WriteResp(w, r, tc.CDNQueueUpdateResponse{Action: reqObj.Action,
CDNID: int64(inf.IntParams["id"])})
}
diff --git a/traffic_ops/traffic_ops_golang/cdn_lock/cdn_lock.go
b/traffic_ops/traffic_ops_golang/cdn_lock/cdn_lock.go
index 4a19500..dbb54ab 100644
--- a/traffic_ops/traffic_ops_golang/cdn_lock/cdn_lock.go
+++ b/traffic_ops/traffic_ops_golang/cdn_lock/cdn_lock.go
@@ -85,6 +85,7 @@ func Read(w http.ResponseWriter, r *http.Request) {
// Create is the handler for POST requests to /cdn_locks.
func Create(w http.ResponseWriter, r *http.Request) {
+ soft := "soft"
inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
@@ -104,6 +105,9 @@ func Create(w http.ResponseWriter, r *http.Request) {
api.HandleErr(w, r, tx, http.StatusBadRequest,
errors.New("field 'soft' must be present"), nil)
return
}
+ if !*cdnLock.Soft {
+ soft = "hard"
+ }
if cdnLock.CDN == "" {
api.HandleErr(w, r, tx, http.StatusBadRequest,
errors.New("field 'cdn' must be present"), nil)
return
@@ -129,10 +133,10 @@ func Create(w http.ResponseWriter, r *http.Request) {
api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
errors.New("cdn lock create: lock couldn't be acquired"))
return
}
- alerts := tc.CreateAlerts(tc.SuccessLevel, "CDN lock acquired!")
+ alerts := tc.CreateAlerts(tc.SuccessLevel, fmt.Sprintf("%s CDN lock
acquired!", soft))
api.WriteAlertsObj(w, r, http.StatusCreated, alerts, cdnLock)
- changeLogMsg := fmt.Sprintf("USER: %s, CDN: %s, ACTION: Lock Acquired",
inf.User.UserName, cdnLock.CDN)
+ changeLogMsg := fmt.Sprintf("USER: %s, CDN: %s, ACTION: %s lock
acquired", inf.User.UserName, cdnLock.CDN, soft)
api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx)
}
diff --git a/traffic_ops/traffic_ops_golang/crconfig/handler.go
b/traffic_ops/traffic_ops_golang/crconfig/handler.go
index 3a85e54..3ba949b 100644
--- a/traffic_ops/traffic_ops_golang/crconfig/handler.go
+++ b/traffic_ops/traffic_ops_golang/crconfig/handler.go
@@ -223,7 +223,11 @@ func snapshotHandler(w http.ResponseWriter, r
*http.Request, deprecated bool) {
return
}
}
-
+ userErr, sysErr, statusCode :=
dbhelpers.CheckIfCurrentUserHasCdnLock(inf.Tx.Tx, cdn, inf.User.UserName)
+ if userErr != nil || sysErr != nil {
+ api.HandleErrOptionalDeprecation(w, r, inf.Tx.Tx, statusCode,
userErr, sysErr, deprecated, &alt)
+ return
+ }
// We never store tm_path, even though low API versions show it in
responses.
crConfig, err := Make(inf.Tx.Tx, cdn, inf.User.UserName, r.Host,
inf.Config.Version, inf.Config.CRConfigUseRequestHost, false)
if err != nil {
diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
index 390d03a..2f84ba2 100644
--- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
+++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
@@ -101,6 +101,31 @@ const getUserByEmailQuery = getUserBaseQuery + `
WHERE tm_user.email = $1
`
+// CheckIfCurrentUserHasCdnLock checks if the current user has the lock on the
cdn that the requested operation is to be performed on.
+// This will succeed if the either there is no lock by any user on the CDN, or
if the current user has the lock on the CDN.
+func CheckIfCurrentUserHasCdnLock(tx *sql.Tx, cdn, user string) (error, error,
int) {
+ query := `SELECT username FROM cdn_lock WHERE cdn=$1`
+ var userName string
+ rows, err := tx.Query(query, cdn)
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ return nil, nil, http.StatusOK
+ }
+ return nil, errors.New("querying cdn_lock for user " + user + "
and cdn " + cdn + ": " + err.Error()), http.StatusInternalServerError
+ }
+ defer rows.Close()
+ for rows.Next() {
+ err = rows.Scan(&userName)
+ if err != nil {
+ return nil, errors.New("scanning cdn_lock for user " +
user + " and cdn " + cdn + ": " + err.Error()), http.StatusInternalServerError
+ }
+ }
+ if userName != "" && user != userName {
+ return errors.New("user " + user + " currently does not have
the lock on cdn " + cdn), nil, http.StatusForbidden
+ }
+ return nil, nil, http.StatusOK
+}
+
func BuildWhereAndOrderByAndPagination(parameters map[string]string,
queryParamsToSQLCols map[string]WhereColumnInfo) (string, string, string,
map[string]interface{}, []error) {
whereClause := BaseWhere
orderBy := BaseOrderBy
@@ -534,6 +559,15 @@ func GetCDNNameFromID(tx *sql.Tx, id int64) (tc.CDNName,
bool, error) {
return tc.CDNName(name), true, nil
}
+// GetCDNNameFromServerID gets the CDN name for the server with the given ID.
+func GetCDNNameFromServerID(tx *sql.Tx, serverId int64) (tc.CDNName, error) {
+ name := ""
+ if err := tx.QueryRow(`SELECT name FROM cdn WHERE id = (SELECT cdn_id
FROM server WHERE id=$1)`, serverId).Scan(&name); err != nil {
+ return "", fmt.Errorf("querying CDN name from server ID: %w",
err)
+ }
+ return tc.CDNName(name), nil
+}
+
// GetCDNIDFromName returns the ID of the CDN if a CDN with the name exists
func GetCDNIDFromName(tx *sql.Tx, name tc.CDNName) (int, bool, error) {
id := 0
diff --git a/traffic_ops/traffic_ops_golang/server/put_status.go
b/traffic_ops/traffic_ops_golang/server/put_status.go
index 8e26a04..b69fb7e 100644
--- a/traffic_ops/traffic_ops_golang/server/put_status.go
+++ b/traffic_ops/traffic_ops_golang/server/put_status.go
@@ -85,7 +85,16 @@ func UpdateStatusHandler(w http.ResponseWriter, r
*http.Request) {
api.HandleErr(w, r, tx, http.StatusNotFound, fmt.Errorf("server
ID %d not found", id), nil)
return
}
-
+ cdnName, err := dbhelpers.GetCDNNameFromServerID(inf.Tx.Tx, int64(id))
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, err)
+ return
+ }
+ userErr, sysErr, statusCode :=
dbhelpers.CheckIfCurrentUserHasCdnLock(inf.Tx.Tx, string(cdnName),
inf.User.UserName)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr)
+ return
+ }
status := tc.StatusNullable{}
statusExists := false
if reqObj.Status.Name != nil {
diff --git a/traffic_ops/traffic_ops_golang/server/queue_update.go
b/traffic_ops/traffic_ops_golang/server/queue_update.go
index cb3689a..c5327b3 100644
--- a/traffic_ops/traffic_ops_golang/server/queue_update.go
+++ b/traffic_ops/traffic_ops_golang/server/queue_update.go
@@ -24,6 +24,7 @@ import (
"encoding/json"
"errors"
"fmt"
+
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
"net/http"
"github.com/apache/trafficcontrol/lib/go-tc"
@@ -54,6 +55,16 @@ func QueueUpdateHandler(w http.ResponseWriter, r
*http.Request) {
serverID := int64(inf.IntParams["id"])
queue := reqObj.Action == "queue"
+ cdnName, err := dbhelpers.GetCDNNameFromServerID(inf.Tx.Tx, serverID)
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, err)
+ return
+ }
+ userErr, sysErr, statusCode :=
dbhelpers.CheckIfCurrentUserHasCdnLock(inf.Tx.Tx, string(cdnName),
inf.User.UserName)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr)
+ return
+ }
ok, err := queueUpdate(inf.Tx.Tx, serverID, queue)
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, fmt.Errorf("queueing updates: %v", err))
diff --git a/traffic_ops/traffic_ops_golang/server/update.go
b/traffic_ops/traffic_ops_golang/server/update.go
index a4e15c1..54ae48d 100644
--- a/traffic_ops/traffic_ops_golang/server/update.go
+++ b/traffic_ops/traffic_ops_golang/server/update.go
@@ -53,15 +53,36 @@ func UpdateHandler(w http.ResponseWriter, r *http.Request) {
return
}
hostName = name
+ cdnName, err := dbhelpers.GetCDNNameFromServerID(inf.Tx.Tx,
int64(id))
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx,
http.StatusInternalServerError, nil, err)
+ return
+ }
+ userErr, sysErr, statusCode :=
dbhelpers.CheckIfCurrentUserHasCdnLock(inf.Tx.Tx, string(cdnName),
inf.User.UserName)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr,
sysErr)
+ return
+ }
} else {
hostName = idOrName
- if _, ok, err := dbhelpers.GetServerIDFromName(hostName,
inf.Tx.Tx); err != nil {
+ serverID, ok, err := dbhelpers.GetServerIDFromName(hostName,
inf.Tx.Tx)
+ if err != nil {
api.HandleErr(w, r, inf.Tx.Tx,
http.StatusInternalServerError, nil, errors.New("getting server id from name
'"+idOrName+"': "+err.Error()))
return
} else if !ok {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound,
errors.New("server name '"+idOrName+"' not found"), nil)
return
}
+ cdnName, err := dbhelpers.GetCDNNameFromServerID(inf.Tx.Tx,
int64(serverID))
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx,
http.StatusInternalServerError, nil, err)
+ return
+ }
+ userErr, sysErr, statusCode :=
dbhelpers.CheckIfCurrentUserHasCdnLock(inf.Tx.Tx, string(cdnName),
inf.User.UserName)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr,
sysErr)
+ return
+ }
}
updated, hasUpdated := inf.Params["updated"]
diff --git a/traffic_ops/traffic_ops_golang/topology/queue_update.go
b/traffic_ops/traffic_ops_golang/topology/queue_update.go
index 14c8f3d..cf07be9 100644
--- a/traffic_ops/traffic_ops_golang/topology/queue_update.go
+++ b/traffic_ops/traffic_ops_golang/topology/queue_update.go
@@ -25,6 +25,7 @@ import (
"errors"
"fmt"
"net/http"
+ "strconv"
validation "github.com/go-ozzo/ozzo-validation"
@@ -76,6 +77,20 @@ func QueueUpdateHandler(w http.ResponseWriter, r
*http.Request) {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest,
fmt.Errorf("invalid request to queue updates: %s", err), nil)
return
}
+ cdnName, ok, err := dbhelpers.GetCDNNameFromID(inf.Tx.Tx, reqObj.CDNID)
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, errors.New("getting CDN name from ID
'"+strconv.Itoa(int(reqObj.CDNID))+"': "+err.Error()))
+ return
+ }
+ if !ok {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest,
errors.New("cdn "+strconv.Itoa(int(reqObj.CDNID))+" does not exist"), nil)
+ return
+ }
+ userErr, sysErr, statusCode :=
dbhelpers.CheckIfCurrentUserHasCdnLock(inf.Tx.Tx, string(cdnName),
inf.User.UserName)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr)
+ return
+ }
if err := queueUpdates(inf.Tx.Tx, topologyName, reqObj.CDNID,
reqObj.Action == "queue"); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, errors.New("Topology queueing updates: "+err.Error()))
return