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

Reply via email to