sts-sponsors team mailing list archive
-
sts-sponsors team
-
Mailing list archive
-
Message #03345
[Merge] ~bjornt/maas:move-authorization-backend into maas:master
Björn Tillenius has proposed merging ~bjornt/maas:move-authorization-backend into maas:master.
Commit message:
Move MAASAuthorizationBackend to its own file.
It shouldn't be in models/__init__.py. I have a separate
branch that moves the metadataserver models to maasserver,
and having MAASAuthorizationBackend in models/__init__.py
caused a lot of circular import problems.
Requested reviews:
MAAS Maintainers (maas-maintainers)
For more details, see:
https://code.launchpad.net/~bjornt/maas/+git/maas/+merge/433292
--
Your team MAAS Maintainers is requested to review the proposed merge of ~bjornt/maas:move-authorization-backend into maas:master.
diff --git a/src/maasserver/auth.py b/src/maasserver/auth.py
new file mode 100644
index 0000000..904484f
--- /dev/null
+++ b/src/maasserver/auth.py
@@ -0,0 +1,459 @@
+from django.contrib.auth.backends import ModelBackend
+from django.contrib.auth.models import User
+
+from maasserver.enum import NODE_TYPE
+from maasserver.models.blockdevice import BlockDevice
+from maasserver.models.bmc import Pod
+from maasserver.models.discovery import Discovery
+from maasserver.models.dnsdata import DNSData
+from maasserver.models.dnsresource import DNSResource
+from maasserver.models.domain import Domain
+from maasserver.models.fabric import Fabric
+from maasserver.models.filesystemgroup import FilesystemGroup
+from maasserver.models.interface import Interface
+from maasserver.models.node import Node
+from maasserver.models.resourcepool import ResourcePool
+from maasserver.models.space import Space
+from maasserver.models.staticroute import StaticRoute
+from maasserver.models.subnet import Subnet
+from maasserver.models.tag import Tag
+from maasserver.models.vlan import VLAN
+from maasserver.models.vmcluster import VMCluster
+from maasserver.permissions import (
+ NodePermission,
+ PodPermission,
+ ResourcePoolPermission,
+ VMClusterPermission,
+)
+from provisioningserver.utils import is_instance_or_subclass
+
+# Some actions are applied to model object types global to MAAS; not
+# necessarily a particular object. The following objects cannot be created or
+# changed by non-administrative users, but superusers can always create, read
+# write, or delete them.
+UNRESTRICTED_READ_MODELS = (
+ DNSData,
+ DNSResource,
+ Domain,
+ Fabric,
+ ResourcePool,
+ Space,
+ Subnet,
+ Tag,
+ StaticRoute,
+ VLAN,
+)
+
+# The following model objects are restricted from non-administrative users.
+# They cannot be seen (or created, or modified, or deleted) by "normal" users.
+ADMIN_RESTRICTED_MODELS = (Discovery,)
+
+# ADMIN_PERMISSIONS applies to the model objects in ADMIN_RESTRICTED_MODELS.
+# These model objects are restricted to administrators only; permission checks
+# will return True for administrators given any of the following permissions:
+ADMIN_PERMISSIONS = (
+ NodePermission.view,
+ NodePermission.edit,
+ NodePermission.admin,
+ NodePermission.admin_read,
+)
+
+
+class MAASAuthorizationBackend(ModelBackend):
+
+ supports_object_permissions = True
+
+ def authenticate(self, request, username=None, password=None, **kwargs):
+ external_auth_info = getattr(request, "external_auth_info", None)
+ # use getattr so that tests that don't include the middleware don't
+ # explode
+ if external_auth_info:
+ # Don't allow username/password logins with external authentication
+ return
+ authenticated = super().authenticate(
+ request, username=username, password=password, **kwargs
+ )
+ if authenticated:
+ user = User.objects.get(username=username)
+ if not user.userprofile.is_local:
+ return
+ return authenticated
+
+ def has_perm(self, user, perm, obj=None):
+ self._sanity_checks(perm, obj=obj)
+ if not user.is_active:
+ # Deactivated users, and in particular the node-init user,
+ # are prohibited from accessing maasserver services.
+ return False
+
+ from maasserver.rbac import rbac
+
+ rbac_enabled = rbac.is_enabled()
+ visible_pools, view_all_pools = [], []
+ deploy_pools, admin_pools = [], []
+ if rbac_enabled:
+ fetched_pools = rbac.get_resource_pool_ids(
+ user.username,
+ "view",
+ "view-all",
+ "deploy-machines",
+ "admin-machines",
+ )
+ visible_pools = fetched_pools["view"]
+ view_all_pools = fetched_pools["view-all"]
+ deploy_pools = fetched_pools["deploy-machines"]
+ admin_pools = fetched_pools["admin-machines"]
+
+ # Handle node permissions without objects.
+ if perm == NodePermission.admin and obj is None:
+ # User wants to admin writes to all nodes (aka. create a node),
+ # must be superuser for those permissions.
+ return user.is_superuser
+ elif perm == NodePermission.view and obj is None:
+ # XXX 2018-11-20 blake_r: View permission without an obj is used
+ # for device create as a standard user. Currently there is no
+ # specific DevicePermission and no way for this code path to know
+ # its for a device. So it is represented using this path.
+ #
+ # View is only used for the create action, modifying a created
+ # device uses the appropriate `NodePermission.edit` scoped to the
+ # device being editted.
+ if rbac_enabled:
+ # User must either be global admin or have access to deploy
+ # or admin some machines.
+ return user.is_superuser or (
+ len(deploy_pools) > 0 or len(admin_pools) > 0
+ )
+ return True
+
+ # ResourcePool permissions are handled specifically.
+ if isinstance(perm, ResourcePoolPermission):
+ return self._perm_resource_pool(
+ user, perm, rbac, visible_pools, obj
+ )
+
+ # Pod permissions are handled specifically.
+ if isinstance(perm, PodPermission):
+ return self._perm_pod(
+ user,
+ perm,
+ rbac,
+ visible_pools,
+ view_all_pools,
+ deploy_pools,
+ admin_pools,
+ obj,
+ )
+
+ if isinstance(perm, VMClusterPermission):
+ return self._perm_vmcluster(
+ user,
+ perm,
+ rbac,
+ visible_pools,
+ view_all_pools,
+ admin_pools,
+ obj,
+ )
+
+ if isinstance(obj, (Node, BlockDevice, FilesystemGroup)):
+ if isinstance(obj, BlockDevice):
+ obj = obj.get_node()
+ elif isinstance(obj, FilesystemGroup):
+ obj = obj.get_node()
+ if perm == NodePermission.view:
+ return self._can_view(
+ rbac_enabled,
+ user,
+ obj,
+ visible_pools,
+ view_all_pools,
+ deploy_pools,
+ admin_pools,
+ )
+ elif perm == NodePermission.edit:
+ can_edit = self._can_edit(
+ rbac_enabled, user, obj, deploy_pools, admin_pools
+ )
+ return not obj.locked and can_edit
+ elif perm == NodePermission.lock:
+ # only machines can be locked
+ can_edit = self._can_edit(
+ rbac_enabled, user, obj, deploy_pools, admin_pools
+ )
+ return obj.pool_id is not None and can_edit
+ elif perm == NodePermission.admin_read:
+ return self._can_admin(rbac_enabled, user, obj, admin_pools)
+ elif perm == NodePermission.admin:
+ return not obj.locked and self._can_admin(
+ rbac_enabled, user, obj, admin_pools
+ )
+ else:
+ raise NotImplementedError(
+ "Invalid permission check (invalid permission name: %s)."
+ % perm
+ )
+ elif isinstance(obj, Interface):
+ node = obj.get_node()
+ if node is None:
+ # Doesn't matter the permission level if the interface doesn't
+ # have a node, the user must be a global admin.
+ return user.is_superuser
+ if perm == NodePermission.view:
+ return self._can_view(
+ rbac_enabled,
+ user,
+ node,
+ visible_pools,
+ view_all_pools,
+ deploy_pools,
+ admin_pools,
+ )
+ elif perm == NodePermission.edit:
+ # Machine interface can only be modified by an administrator
+ # of the machine. Even the owner of the machine cannot modify
+ # the interfaces on that machine, unless they have
+ # administrator rights.
+ if node.node_type == NODE_TYPE.MACHINE:
+ return self._can_admin(
+ rbac_enabled, user, node, admin_pools
+ )
+ # Other node types must be editable by the user.
+ return self._can_edit(
+ rbac_enabled, user, node, deploy_pools, admin_pools
+ )
+ elif perm == NodePermission.admin:
+ # Admin permission is solely granted to superusers.
+ return self._can_admin(rbac_enabled, user, node, admin_pools)
+ else:
+ raise NotImplementedError(
+ "Invalid permission check (invalid permission name: %s)."
+ % perm
+ )
+ elif is_instance_or_subclass(obj, UNRESTRICTED_READ_MODELS):
+ # This model is classified under 'unrestricted read' for any
+ # logged-in user; so everyone can view, but only an admin can
+ # do anything else.
+ if perm == NodePermission.view:
+ return True
+ elif perm in ADMIN_PERMISSIONS:
+ # Admin permission is solely granted to superusers.
+ return user.is_superuser
+ else:
+ raise NotImplementedError(
+ "Invalid permission check (invalid permission name: %s)."
+ % perm
+ )
+ elif is_instance_or_subclass(obj, ADMIN_RESTRICTED_MODELS):
+ # Only administrators are allowed to read/write these objects.
+ if perm in ADMIN_PERMISSIONS:
+ return user.is_superuser
+ else:
+ raise NotImplementedError(
+ "Invalid permission check (invalid permission name: %s)."
+ % perm
+ )
+ else:
+ raise NotImplementedError(
+ "Invalid permission check (invalid object type)."
+ )
+
+ def _sanity_checks(self, perm, obj=None):
+ """Perform sanity checks to ensure that the perm matches the object."""
+ # Sanity check that a `ResourcePool` is being checked against
+ # `ResourcePoolPermission`.
+ if (
+ obj is not None
+ and isinstance(obj, ResourcePool)
+ and not isinstance(perm, ResourcePoolPermission)
+ ):
+ raise TypeError(
+ "obj type of ResourcePool must be checked "
+ "against a `ResourcePoolPermission`."
+ )
+
+ # Sanity check that a `Pod` is being checked against `PodPermission`.
+ if (
+ obj is not None
+ and isinstance(obj, Pod)
+ and not isinstance(perm, PodPermission)
+ ):
+ raise TypeError(
+ "obj type of Pod must be checked against a `PodPermission`."
+ )
+
+ def _can_view(
+ self,
+ rbac_enabled,
+ user,
+ machine,
+ visible_pools,
+ view_all_pools,
+ deploy_pools,
+ admin_pools,
+ ):
+ if machine.pool_id is None:
+ # Only machines are filtered for view access.
+ return True
+ if rbac_enabled:
+ # Machine not owned by the user must be in the view_all_pools or
+ # admin_pools for the user to be able to view the machine.
+ if machine.owner_id is not None and machine.owner_id != user.id:
+ return (
+ machine.pool_id in view_all_pools
+ or machine.pool_id in admin_pools
+ )
+ # Machine is not owned or owned by the user so must be in either
+ # pool for the user to view it.
+ return (
+ machine.pool_id in visible_pools
+ or machine.pool_id in view_all_pools
+ or machine.pool_id in deploy_pools
+ or machine.pool_id in admin_pools
+ )
+ return (
+ machine.owner_id is None
+ or machine.owner_id == user.id
+ or user.is_superuser
+ )
+
+ def _can_edit(
+ self, rbac_enabled, user, machine, deploy_pools, admin_pools
+ ):
+ editable = machine.owner_id is None or machine.owner_id == user.id
+ if rbac_enabled:
+ can_admin = self._can_admin(
+ rbac_enabled, user, machine, admin_pools
+ )
+ can_edit = (
+ machine.pool_id in deploy_pools
+ or (machine.pool_id is None and machine.owner == user)
+ or can_admin
+ )
+ return (editable and can_edit) or can_admin
+ return editable or user.is_superuser
+
+ def _can_admin(self, rbac_enabled, user, machine, admin_pools):
+ if machine.pool_id is None:
+ # Not a machine to be admin on this must have global admin.
+ return user.is_superuser
+ if rbac_enabled:
+ return machine.pool_id in admin_pools
+ return user.is_superuser
+
+ def _perm_resource_pool(self, user, perm, rbac, visible_pools, obj=None):
+ # `create` permissions is called without an `obj`.
+ rbac_enabled = rbac.is_enabled()
+ if perm == ResourcePoolPermission.create:
+ if rbac_enabled:
+ return rbac.can_create_resource_pool(user.username)
+ return user.is_superuser
+ if perm == ResourcePoolPermission.delete:
+ if rbac_enabled:
+ return rbac.can_delete_resource_pool(user.username)
+ return user.is_superuser
+
+ # From this point forward the `obj` must be a `ResourcePool`.
+ if not isinstance(obj, ResourcePool):
+ raise ValueError(
+ "only `ResourcePoolPermission.(create|delete)` can be used "
+ "without an `obj`."
+ )
+
+ if perm == ResourcePoolPermission.edit:
+ if rbac_enabled:
+ return (
+ obj.id
+ in rbac.get_resource_pool_ids(user.username, "edit")[
+ "edit"
+ ]
+ )
+ return user.is_superuser
+ elif perm == ResourcePoolPermission.view:
+ if rbac_enabled:
+ return obj.id in visible_pools
+ return True
+
+ raise ValueError("unknown ResourcePoolPermission value: %s" % perm)
+
+ def _perm_pod(
+ self,
+ user,
+ perm,
+ rbac,
+ visible_pools,
+ view_all_pools,
+ deploy_pools,
+ admin_pools,
+ obj=None,
+ ):
+ # `create` permissions is called without an `obj`.
+ rbac_enabled = rbac.is_enabled()
+ if perm == PodPermission.create:
+ return user.is_superuser
+
+ # From this point forward the `obj` must be a `ResourcePool`.
+ if not isinstance(obj, Pod):
+ raise ValueError(
+ "only `PodPermission.create` can be used without an `obj`."
+ )
+
+ if perm == PodPermission.edit:
+ if rbac_enabled:
+ return obj.pool_id in admin_pools
+ return user.is_superuser
+ elif perm == PodPermission.compose:
+ if rbac_enabled:
+ return obj.pool_id in admin_pools
+ return user.is_superuser
+ elif perm == PodPermission.dynamic_compose:
+ if rbac_enabled:
+ return (
+ obj.pool_id in deploy_pools or obj.pool_id in admin_pools
+ )
+ return True
+ elif perm == PodPermission.view:
+ if rbac_enabled:
+ return (
+ obj.pool_id in visible_pools
+ or obj.pool_id in view_all_pools
+ )
+ return True
+
+ raise ValueError("unknown PodPermission value: %s" % perm)
+
+ def _perm_vmcluster(
+ self,
+ user,
+ perm,
+ rbac,
+ visible_pools,
+ view_all_pools,
+ admin_pools,
+ obj=None,
+ ):
+ rbac_enabled = rbac.is_enabled()
+ if not isinstance(obj, VMCluster):
+ raise ValueError(
+ "`VMClusterPermission` requires an `obj` of type `VMCluster`"
+ )
+
+ if perm == VMClusterPermission.view:
+ if rbac_enabled:
+ return (
+ obj.pool_id in visible_pools
+ or obj.pool_id in view_all_pools
+ )
+ return True
+
+ if perm == VMClusterPermission.edit:
+ if rbac_enabled:
+ return obj.pool_id in admin_pools
+ return user.is_superuser
+
+ if perm == VMClusterPermission.delete:
+ if rbac_enabled:
+ return obj.pool_id in admin_pools
+ return user.is_superuser
+
+ raise ValueError("unknown VMClusterPermission value: %s" % perm)
diff --git a/src/maasserver/djangosettings/settings.py b/src/maasserver/djangosettings/settings.py
index ba47b2f..9642514 100644
--- a/src/maasserver/djangosettings/settings.py
+++ b/src/maasserver/djangosettings/settings.py
@@ -142,7 +142,7 @@ PISTON_DISPLAY_ERRORS = False
PISTON_IGNORE_DUPE_MODELS = True
AUTHENTICATION_BACKENDS = (
- "maasserver.models.MAASAuthorizationBackend",
+ "maasserver.auth.MAASAuthorizationBackend",
"maasserver.macaroon_auth.MacaroonAuthorizationBackend",
)
diff --git a/src/maasserver/forms/__init__.py b/src/maasserver/forms/__init__.py
index 93e90af..4be92fa 100644
--- a/src/maasserver/forms/__init__.py
+++ b/src/maasserver/forms/__init__.py
@@ -150,18 +150,18 @@ from maasserver.models import (
PhysicalBlockDevice,
RAID,
ResourcePool,
- SSHKey,
- SSLKey,
- Tag,
- VirtualBlockDevice,
VMFS,
VolumeGroup,
- Zone,
)
from maasserver.models.blockdevice import MIN_BLOCK_DEVICE_SIZE
from maasserver.models.bootresource import LINUX_OSYSTEMS
from maasserver.models.partition import MIN_PARTITION_SIZE
from maasserver.models.partitiontable import PARTITION_TABLE_TYPE_CHOICES
+from maasserver.models.sshkey import SSHKey
+from maasserver.models.sslkey import SSLKey
+from maasserver.models.tag import Tag
+from maasserver.models.virtualblockdevice import VirtualBlockDevice
+from maasserver.models.zone import Zone
from maasserver.permissions import NodePermission, ResourcePoolPermission
from maasserver.storage_layouts import VMFS6StorageLayout, VMFS7StorageLayout
from maasserver.utils.certificates import generate_certificate
diff --git a/src/maasserver/macaroon_auth.py b/src/maasserver/macaroon_auth.py
index 7587788..6143214 100644
--- a/src/maasserver/macaroon_auth.py
+++ b/src/maasserver/macaroon_auth.py
@@ -24,7 +24,8 @@ from macaroonbakery.httpbakery.agent import Agent, AgentInteractor, AuthInfo
from piston3.utils import rc
import requests
-from maasserver.models import MAASAuthorizationBackend, RootKey
+from maasserver.auth import MAASAuthorizationBackend
+from maasserver.models.rootkey import RootKey
from maasserver.models.user import SYSTEM_USERS
from maasserver.utils.views import request_headers
from provisioningserver.security import to_bin, to_hex
diff --git a/src/maasserver/models/__init__.py b/src/maasserver/models/__init__.py
index 3126441..72f3ef5 100644
--- a/src/maasserver/models/__init__.py
+++ b/src/maasserver/models/__init__.py
@@ -98,7 +98,6 @@ __all__ = [
"Zone",
]
-from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import _user_has_perm, User, UserManager
from django.core.exceptions import ViewDoesNotExist
from django.db.models.signals import post_save
@@ -106,7 +105,6 @@ from django.urls import get_callable, get_resolver, get_script_prefix
from piston3.doc import HandlerDocumentation
from maasserver import logger
-from maasserver.enum import NODE_TYPE
from maasserver.models.blockdevice import BlockDevice
from maasserver.models.bmc import (
BMC,
@@ -205,13 +203,6 @@ from maasserver.models.virtualmachine import VirtualMachine
from maasserver.models.vlan import VLAN
from maasserver.models.vmcluster import VMCluster
from maasserver.models.zone import Zone
-from maasserver.permissions import (
- NodePermission,
- PodPermission,
- ResourcePoolPermission,
- VMClusterPermission,
-)
-from provisioningserver.utils import is_instance_or_subclass
# Connect post-creation methods for models.
post_save.connect(create_user, sender=User)
@@ -298,437 +289,6 @@ HandlerDocumentation.resource_uri_template = property(
get_resource_uri_template
)
-# Some actions are applied to model object types global to MAAS; not
-# necessarily a particular object. The following objects cannot be created or
-# changed by non-administrative users, but superusers can always create, read
-# write, or delete them.
-UNRESTRICTED_READ_MODELS = (
- DNSData,
- DNSResource,
- Domain,
- Fabric,
- ResourcePool,
- Space,
- Subnet,
- Tag,
- StaticRoute,
- VLAN,
-)
-
-# The following model objects are restricted from non-administrative users.
-# They cannot be seen (or created, or modified, or deleted) by "normal" users.
-ADMIN_RESTRICTED_MODELS = (Discovery,)
-
-# ADMIN_PERMISSIONS applies to the model objects in ADMIN_RESTRICTED_MODELS.
-# These model objects are restricted to administrators only; permission checks
-# will return True for administrators given any of the following permissions:
-ADMIN_PERMISSIONS = (
- NodePermission.view,
- NodePermission.edit,
- NodePermission.admin,
- NodePermission.admin_read,
-)
-
-
-class MAASAuthorizationBackend(ModelBackend):
-
- supports_object_permissions = True
-
- def authenticate(self, request, username=None, password=None, **kwargs):
- external_auth_info = getattr(request, "external_auth_info", None)
- # use getattr so that tests that don't include the middleware don't
- # explode
- if external_auth_info:
- # Don't allow username/password logins with external authentication
- return
- authenticated = super().authenticate(
- request, username=username, password=password, **kwargs
- )
- if authenticated:
- user = User.objects.get(username=username)
- if not user.userprofile.is_local:
- return
- return authenticated
-
- def has_perm(self, user, perm, obj=None):
- self._sanity_checks(perm, obj=obj)
- if not user.is_active:
- # Deactivated users, and in particular the node-init user,
- # are prohibited from accessing maasserver services.
- return False
-
- from maasserver.rbac import rbac
-
- rbac_enabled = rbac.is_enabled()
- visible_pools, view_all_pools = [], []
- deploy_pools, admin_pools = [], []
- if rbac_enabled:
- fetched_pools = rbac.get_resource_pool_ids(
- user.username,
- "view",
- "view-all",
- "deploy-machines",
- "admin-machines",
- )
- visible_pools = fetched_pools["view"]
- view_all_pools = fetched_pools["view-all"]
- deploy_pools = fetched_pools["deploy-machines"]
- admin_pools = fetched_pools["admin-machines"]
-
- # Handle node permissions without objects.
- if perm == NodePermission.admin and obj is None:
- # User wants to admin writes to all nodes (aka. create a node),
- # must be superuser for those permissions.
- return user.is_superuser
- elif perm == NodePermission.view and obj is None:
- # XXX 2018-11-20 blake_r: View permission without an obj is used
- # for device create as a standard user. Currently there is no
- # specific DevicePermission and no way for this code path to know
- # its for a device. So it is represented using this path.
- #
- # View is only used for the create action, modifying a created
- # device uses the appropriate `NodePermission.edit` scoped to the
- # device being editted.
- if rbac_enabled:
- # User must either be global admin or have access to deploy
- # or admin some machines.
- return user.is_superuser or (
- len(deploy_pools) > 0 or len(admin_pools) > 0
- )
- return True
-
- # ResourcePool permissions are handled specifically.
- if isinstance(perm, ResourcePoolPermission):
- return self._perm_resource_pool(
- user, perm, rbac, visible_pools, obj
- )
-
- # Pod permissions are handled specifically.
- if isinstance(perm, PodPermission):
- return self._perm_pod(
- user,
- perm,
- rbac,
- visible_pools,
- view_all_pools,
- deploy_pools,
- admin_pools,
- obj,
- )
-
- if isinstance(perm, VMClusterPermission):
- return self._perm_vmcluster(
- user,
- perm,
- rbac,
- visible_pools,
- view_all_pools,
- admin_pools,
- obj,
- )
-
- if isinstance(obj, (Node, BlockDevice, FilesystemGroup)):
- if isinstance(obj, BlockDevice):
- obj = obj.get_node()
- elif isinstance(obj, FilesystemGroup):
- obj = obj.get_node()
- if perm == NodePermission.view:
- return self._can_view(
- rbac_enabled,
- user,
- obj,
- visible_pools,
- view_all_pools,
- deploy_pools,
- admin_pools,
- )
- elif perm == NodePermission.edit:
- can_edit = self._can_edit(
- rbac_enabled, user, obj, deploy_pools, admin_pools
- )
- return not obj.locked and can_edit
- elif perm == NodePermission.lock:
- # only machines can be locked
- can_edit = self._can_edit(
- rbac_enabled, user, obj, deploy_pools, admin_pools
- )
- return obj.pool_id is not None and can_edit
- elif perm == NodePermission.admin_read:
- return self._can_admin(rbac_enabled, user, obj, admin_pools)
- elif perm == NodePermission.admin:
- return not obj.locked and self._can_admin(
- rbac_enabled, user, obj, admin_pools
- )
- else:
- raise NotImplementedError(
- "Invalid permission check (invalid permission name: %s)."
- % perm
- )
- elif isinstance(obj, Interface):
- node = obj.get_node()
- if node is None:
- # Doesn't matter the permission level if the interface doesn't
- # have a node, the user must be a global admin.
- return user.is_superuser
- if perm == NodePermission.view:
- return self._can_view(
- rbac_enabled,
- user,
- node,
- visible_pools,
- view_all_pools,
- deploy_pools,
- admin_pools,
- )
- elif perm == NodePermission.edit:
- # Machine interface can only be modified by an administrator
- # of the machine. Even the owner of the machine cannot modify
- # the interfaces on that machine, unless they have
- # administrator rights.
- if node.node_type == NODE_TYPE.MACHINE:
- return self._can_admin(
- rbac_enabled, user, node, admin_pools
- )
- # Other node types must be editable by the user.
- return self._can_edit(
- rbac_enabled, user, node, deploy_pools, admin_pools
- )
- elif perm == NodePermission.admin:
- # Admin permission is solely granted to superusers.
- return self._can_admin(rbac_enabled, user, node, admin_pools)
- else:
- raise NotImplementedError(
- "Invalid permission check (invalid permission name: %s)."
- % perm
- )
- elif is_instance_or_subclass(obj, UNRESTRICTED_READ_MODELS):
- # This model is classified under 'unrestricted read' for any
- # logged-in user; so everyone can view, but only an admin can
- # do anything else.
- if perm == NodePermission.view:
- return True
- elif perm in ADMIN_PERMISSIONS:
- # Admin permission is solely granted to superusers.
- return user.is_superuser
- else:
- raise NotImplementedError(
- "Invalid permission check (invalid permission name: %s)."
- % perm
- )
- elif is_instance_or_subclass(obj, ADMIN_RESTRICTED_MODELS):
- # Only administrators are allowed to read/write these objects.
- if perm in ADMIN_PERMISSIONS:
- return user.is_superuser
- else:
- raise NotImplementedError(
- "Invalid permission check (invalid permission name: %s)."
- % perm
- )
- else:
- raise NotImplementedError(
- "Invalid permission check (invalid object type)."
- )
-
- def _sanity_checks(self, perm, obj=None):
- """Perform sanity checks to ensure that the perm matches the object."""
- # Sanity check that a `ResourcePool` is being checked against
- # `ResourcePoolPermission`.
- if (
- obj is not None
- and isinstance(obj, ResourcePool)
- and not isinstance(perm, ResourcePoolPermission)
- ):
- raise TypeError(
- "obj type of ResourcePool must be checked "
- "against a `ResourcePoolPermission`."
- )
-
- # Sanity check that a `Pod` is being checked against `PodPermission`.
- if (
- obj is not None
- and isinstance(obj, Pod)
- and not isinstance(perm, PodPermission)
- ):
- raise TypeError(
- "obj type of Pod must be checked against a `PodPermission`."
- )
-
- def _can_view(
- self,
- rbac_enabled,
- user,
- machine,
- visible_pools,
- view_all_pools,
- deploy_pools,
- admin_pools,
- ):
- if machine.pool_id is None:
- # Only machines are filtered for view access.
- return True
- if rbac_enabled:
- # Machine not owned by the user must be in the view_all_pools or
- # admin_pools for the user to be able to view the machine.
- if machine.owner_id is not None and machine.owner_id != user.id:
- return (
- machine.pool_id in view_all_pools
- or machine.pool_id in admin_pools
- )
- # Machine is not owned or owned by the user so must be in either
- # pool for the user to view it.
- return (
- machine.pool_id in visible_pools
- or machine.pool_id in view_all_pools
- or machine.pool_id in deploy_pools
- or machine.pool_id in admin_pools
- )
- return (
- machine.owner_id is None
- or machine.owner_id == user.id
- or user.is_superuser
- )
-
- def _can_edit(
- self, rbac_enabled, user, machine, deploy_pools, admin_pools
- ):
- editable = machine.owner_id is None or machine.owner_id == user.id
- if rbac_enabled:
- can_admin = self._can_admin(
- rbac_enabled, user, machine, admin_pools
- )
- can_edit = (
- machine.pool_id in deploy_pools
- or (machine.pool_id is None and machine.owner == user)
- or can_admin
- )
- return (editable and can_edit) or can_admin
- return editable or user.is_superuser
-
- def _can_admin(self, rbac_enabled, user, machine, admin_pools):
- if machine.pool_id is None:
- # Not a machine to be admin on this must have global admin.
- return user.is_superuser
- if rbac_enabled:
- return machine.pool_id in admin_pools
- return user.is_superuser
-
- def _perm_resource_pool(self, user, perm, rbac, visible_pools, obj=None):
- # `create` permissions is called without an `obj`.
- rbac_enabled = rbac.is_enabled()
- if perm == ResourcePoolPermission.create:
- if rbac_enabled:
- return rbac.can_create_resource_pool(user.username)
- return user.is_superuser
- if perm == ResourcePoolPermission.delete:
- if rbac_enabled:
- return rbac.can_delete_resource_pool(user.username)
- return user.is_superuser
-
- # From this point forward the `obj` must be a `ResourcePool`.
- if not isinstance(obj, ResourcePool):
- raise ValueError(
- "only `ResourcePoolPermission.(create|delete)` can be used "
- "without an `obj`."
- )
-
- if perm == ResourcePoolPermission.edit:
- if rbac_enabled:
- return (
- obj.id
- in rbac.get_resource_pool_ids(user.username, "edit")[
- "edit"
- ]
- )
- return user.is_superuser
- elif perm == ResourcePoolPermission.view:
- if rbac_enabled:
- return obj.id in visible_pools
- return True
-
- raise ValueError("unknown ResourcePoolPermission value: %s" % perm)
-
- def _perm_pod(
- self,
- user,
- perm,
- rbac,
- visible_pools,
- view_all_pools,
- deploy_pools,
- admin_pools,
- obj=None,
- ):
- # `create` permissions is called without an `obj`.
- rbac_enabled = rbac.is_enabled()
- if perm == PodPermission.create:
- return user.is_superuser
-
- # From this point forward the `obj` must be a `ResourcePool`.
- if not isinstance(obj, Pod):
- raise ValueError(
- "only `PodPermission.create` can be used without an `obj`."
- )
-
- if perm == PodPermission.edit:
- if rbac_enabled:
- return obj.pool_id in admin_pools
- return user.is_superuser
- elif perm == PodPermission.compose:
- if rbac_enabled:
- return obj.pool_id in admin_pools
- return user.is_superuser
- elif perm == PodPermission.dynamic_compose:
- if rbac_enabled:
- return (
- obj.pool_id in deploy_pools or obj.pool_id in admin_pools
- )
- return True
- elif perm == PodPermission.view:
- if rbac_enabled:
- return (
- obj.pool_id in visible_pools
- or obj.pool_id in view_all_pools
- )
- return True
-
- raise ValueError("unknown PodPermission value: %s" % perm)
-
- def _perm_vmcluster(
- self,
- user,
- perm,
- rbac,
- visible_pools,
- view_all_pools,
- admin_pools,
- obj=None,
- ):
- rbac_enabled = rbac.is_enabled()
- if not isinstance(obj, VMCluster):
- raise ValueError(
- "`VMClusterPermission` requires an `obj` of type `VMCluster`"
- )
-
- if perm == VMClusterPermission.view:
- if rbac_enabled:
- return (
- obj.pool_id in visible_pools
- or obj.pool_id in view_all_pools
- )
- return True
-
- if perm == VMClusterPermission.edit:
- if rbac_enabled:
- return obj.pool_id in admin_pools
- return user.is_superuser
-
- if perm == VMClusterPermission.delete:
- if rbac_enabled:
- return obj.pool_id in admin_pools
- return user.is_superuser
-
- raise ValueError("unknown VMClusterPermission value: %s" % perm)
-
# Ensure that all signals modules are loaded.
from maasserver.models import signals # noqa:E402 isort:skip
diff --git a/src/maasserver/preseed_network.py b/src/maasserver/preseed_network.py
index 7660feb..03073f7 100644
--- a/src/maasserver/preseed_network.py
+++ b/src/maasserver/preseed_network.py
@@ -18,7 +18,8 @@ from maasserver.enum import (
IPADDRESS_TYPE,
NODE_STATUS,
)
-from maasserver.models import Interface, StaticIPAddress
+from maasserver.models import Interface
+from maasserver.models.staticipaddress import StaticIPAddress
from maasserver.models.staticroute import StaticRoute
from provisioningserver.utils.netplan import (
get_netplan_bond_parameters,
diff --git a/src/maasserver/preseed_storage.py b/src/maasserver/preseed_storage.py
index b800872..2a0b0ce 100644
--- a/src/maasserver/preseed_storage.py
+++ b/src/maasserver/preseed_storage.py
@@ -14,18 +14,14 @@ from maasserver.enum import (
FILESYSTEM_TYPE,
PARTITION_TABLE_TYPE,
)
-from maasserver.models import (
- FilesystemGroup,
- Partition,
- PhysicalBlockDevice,
- VirtualBlockDevice,
-)
+from maasserver.models import FilesystemGroup, Partition, PhysicalBlockDevice
from maasserver.models.partitiontable import (
BIOS_GRUB_PARTITION_SIZE,
INITIAL_PARTITION_OFFSET,
PARTITION_TABLE_EXTRA_SPACE,
PREP_PARTITION_SIZE,
)
+from maasserver.models.virtualblockdevice import VirtualBlockDevice
class CurtinStorageGenerator:
diff --git a/src/maasserver/tests/test_auth.py b/src/maasserver/tests/test_auth.py
index 3fbba87..0a67fb4 100644
--- a/src/maasserver/tests/test_auth.py
+++ b/src/maasserver/tests/test_auth.py
@@ -6,9 +6,10 @@ import http.client
from django.urls import reverse
+from maasserver.auth import MAASAuthorizationBackend
from maasserver.enum import INTERFACE_TYPE, NODE_STATUS
from maasserver.middleware import ExternalAuthInfoMiddleware
-from maasserver.models import MAASAuthorizationBackend, Node
+from maasserver.models import Node
from maasserver.permissions import (
NodePermission,
PodPermission,
diff --git a/src/metadataserver/builtin_scripts/hooks.py b/src/metadataserver/builtin_scripts/hooks.py
index 392f533..8d1dbc9 100644
--- a/src/metadataserver/builtin_scripts/hooks.py
+++ b/src/metadataserver/builtin_scripts/hooks.py
@@ -30,10 +30,10 @@ from maasserver.models import (
NUMANodeHugepages,
PhysicalBlockDevice,
PhysicalInterface,
- Subnet,
- Tag,
)
from maasserver.models.blockdevice import MIN_BLOCK_DEVICE_SIZE
+from maasserver.models.subnet import Subnet
+from maasserver.models.tag import Tag
from maasserver.storage_custom import (
apply_layout_to_machine,
get_storage_layout,
Follow ups