From 5fc25952caa917c5406a6e623039377dc55c0b50 Mon Sep 17 00:00:00 2001
From: Benjamin Bertrand <benjamin.bertrand@ess.eu>
Date: Tue, 28 Jan 2020 15:13:57 +0100
Subject: [PATCH] Restrict view of networks and scopes to admin only

JIRA INFRA-1671 #action In Progress
---
 app/api/network.py           |  4 ++--
 app/network/views.py         | 10 ++++++----
 tests/functional/test_api.py | 14 +++++++++-----
 tests/functional/test_web.py | 32 +++++++++++++++++++++++++++++++-
 4 files changed, 48 insertions(+), 12 deletions(-)

diff --git a/app/api/network.py b/app/api/network.py
index 0373fa0..73b9d10 100644
--- a/app/api/network.py
+++ b/app/api/network.py
@@ -21,7 +21,7 @@ bp = Blueprint("network_api", __name__)
 
 
 @bp.route("/scopes")
-@login_required
+@login_groups_accepted("admin")
 def get_scopes():
     """Return network scopes
 
@@ -53,7 +53,7 @@ def create_scope():
 
 
 @bp.route("/networks")
-@login_required
+@login_groups_accepted("admin")
 def get_networks():
     """Return networks
 
diff --git a/app/network/views.py b/app/network/views.py
index 631c124..d405218 100644
--- a/app/network/views.py
+++ b/app/network/views.py
@@ -570,14 +570,14 @@ def create_domain():
 
 
 @bp.route("/scopes")
-@login_required
+@login_groups_accepted("admin")
 def list_scopes():
     scopes = models.NetworkScope.query.all()
     return render_template("network/scopes.html", scopes=scopes)
 
 
 @bp.route("/scopes/view/<name>")
-@login_required
+@login_groups_accepted("admin")
 def view_scope(name):
     scope = models.NetworkScope.query.filter_by(name=name).first_or_404()
     return render_template("network/view_scope.html", scope=scope)
@@ -630,16 +630,18 @@ def retrieve_first_available_ip(network_id):
 
 
 @bp.route("/networks")
-@login_required
+@login_groups_accepted("admin")
 def list_networks():
     networks = models.Network.query.all()
     return render_template("network/networks.html", networks=networks)
 
 
 @bp.route("/networks/view/<vlan_name>")
-@login_required
+@login_groups_accepted("admin", "network")
 def view_network(vlan_name):
     network = models.Network.query.filter_by(vlan_name=vlan_name).first_or_404()
+    if not current_user.has_access_to_network(network):
+        abort(403)
     return render_template("network/view_network.html", network=network)
 
 
diff --git a/tests/functional/test_api.py b/tests/functional/test_api.py
index a33542f..d4276e4 100644
--- a/tests/functional/test_api.py
+++ b/tests/functional/test_api.py
@@ -615,7 +615,13 @@ def test_get_items(client, location_factory, item_factory, readonly_token):
     check_response_message(response, "Invalid query arguments", 422)
 
 
-def test_get_networks(client, network_scope_factory, network_factory, readonly_token):
+@pytest.mark.parametrize("url", ["/network/networks", "/network/scopes"])
+def test_get_restricted_url_as_non_admin(url, client, readonly_token):
+    response = get(client, f"{API_URL}{url}", token=readonly_token)
+    assert response.status_code == 403
+
+
+def test_get_networks(client, network_scope_factory, network_factory, admin_token):
     # Create some networks
     scope = network_scope_factory(supernet="172.16.0.0/16")
     network1 = network_factory(
@@ -637,7 +643,7 @@ def test_get_networks(client, network_scope_factory, network_factory, readonly_t
         scope=scope,
     )
 
-    response = get(client, f"{API_URL}/network/networks", token=readonly_token)
+    response = get(client, f"{API_URL}/network/networks", token=admin_token)
     assert response.status_code == 200
     assert len(response.get_json()) == 3
     check_input_is_subset_of_response(
@@ -646,9 +652,7 @@ def test_get_networks(client, network_scope_factory, network_factory, readonly_t
 
     # test filtering by address
     response = get(
-        client,
-        f"{API_URL}/network/networks?address=172.16.20.0/22",
-        token=readonly_token,
+        client, f"{API_URL}/network/networks?address=172.16.20.0/22", token=admin_token,
     )
     assert response.status_code == 200
     assert len(response.get_json()) == 1
diff --git a/tests/functional/test_web.py b/tests/functional/test_web.py
index 0b08160..dcee73e 100644
--- a/tests/functional/test_web.py
+++ b/tests/functional/test_web.py
@@ -74,7 +74,7 @@ def test_index(logged_client):
     assert b"User RO" in response.data
 
 
-@pytest.mark.parametrize("url", ["/", "/inventory/items", "/network/networks"])
+@pytest.mark.parametrize("url", ["/", "/inventory/items", "/network/hosts"])
 def test_protected_url_get(url, client):
     response = client.get(url)
     assert response.status_code == 302
@@ -84,6 +84,17 @@ def test_protected_url_get(url, client):
     assert response.status_code == 200
 
 
+@pytest.mark.parametrize("url", ["/network/networks", "/network/scopes"])
+def test_admin_protected_url_get(url, client):
+    login(client, "user_rw", "userrw")
+    response = client.get(url)
+    assert response.status_code == 403
+    logout(client)
+    login(client, "admin", "adminpasswd")
+    response = client.get(url)
+    assert response.status_code == 200
+
+
 @pytest.mark.parametrize(
     "url", ["/inventory/_retrieve_items", "/network/_retrieve_hosts"]
 )
@@ -804,3 +815,22 @@ def test_create_network_scope_overlapping(network_scope_factory, logged_admin_cl
     response = logged_admin_client.post("/network/scopes/create", data=form)
     assert response.status_code == 200
     assert b"172.30.200.0/22 overlaps scope1 (172.30.0.0/16)" in response.data
+
+
+def test_view_network_restriction(client, network_scope_factory, network_factory):
+    scope = network_scope_factory(name="ProdNetworks", supernet="192.168.0.0/16")
+    network = network_factory(
+        address="192.168.1.0/24",
+        first_ip="192.168.1.10",
+        last_ip="192.168.1.250",
+        scope=scope,
+    )
+    # user_lab doesn't have the permissions to see that network
+    login(client, "user_lab", "userlab")
+    response = client.get(f"/network/networks/view/{network}")
+    assert response.status_code == 403
+    logout(client)
+    # Success with user_prod user
+    login(client, "user_prod", "userprod")
+    response = client.get(f"/network/networks/view/{network}")
+    assert response.status_code == 200
-- 
GitLab