from magpie.api.exception import evaluate_call
from magpie.definitions.pyramid_definitions import ALLOW, ALL_PERMISSIONS, HTTPInternalServerError
from magpie.definitions.sqlalchemy_definitions import sa, declared_attr, relationship, declarative_base
from magpie.definitions.ziggurat_definitions import (
get_db_session,
permission_to_pyramid_acls,
ziggurat_model_init,
BaseModel,
ExternalIdentityMixin,
GroupMixin,
GroupPermissionMixin,
GroupResourcePermissionMixin,
ResourceMixin,
ResourceTreeService,
ResourceTreeServicePostgreSQL,
UserGroupMixin,
UserMixin,
UserPermissionMixin,
UserResourcePermissionMixin,
UserService,
BaseService,
)
from magpie.permissions import Permission
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from magpie.definitions.typedefs import Str # noqa: F401
[docs]Base = declarative_base()
[docs]def get_session_callable(request):
return request.db
[docs]class Group(GroupMixin, Base):
[docs] def get_member_count(self, db_session=None):
return BaseService.all(UserGroup, db_session=db_session).filter(UserGroup.group_id == self.id).count()
[docs]class GroupPermission(GroupPermissionMixin, Base):
pass
[docs]class UserGroup(UserGroupMixin, Base):
pass
[docs]class GroupResourcePermission(GroupResourcePermissionMixin, Base):
pass
[docs]class Resource(ResourceMixin, Base):
# required resource type identifier (unique)
[docs] resource_type_name = None # type: Str
[docs] child_resource_allowed = True
[docs] resource_display_name = sa.Column(sa.Unicode(100), nullable=True)
# reference to top-most service under which the resource is nested
# if the resource is the service, id is None (NULL)
@declared_attr
[docs] def root_service_id(self):
return sa.Column(sa.Integer,
sa.ForeignKey("services.resource_id",
onupdate="CASCADE",
ondelete="SET NULL"), index=True)
@property
[docs] def __acl__(self):
acl = []
if self.owner_user_id:
acl.extend([(ALLOW, self.owner_user_id, ALL_PERMISSIONS,), ])
if self.owner_group_id:
acl.extend([(ALLOW, "group:%s" % self.owner_group_id, ALL_PERMISSIONS,), ])
return acl
[docs]class UserPermission(UserPermissionMixin, Base):
pass
[docs]class UserResourcePermission(UserResourcePermissionMixin, Base):
pass
[docs]class User(UserMixin, Base):
[docs] def __str__(self):
return "<User: %s, %s>" % (self.id, self.user_name)
[docs]class ExternalIdentity(ExternalIdentityMixin, Base):
pass
[docs]class RootFactory(object):
def __init__(self, request):
self.__acl__ = []
if request.user:
permissions = UserService.permissions(request.user, request.db)
self.__acl__.extend(permission_to_pyramid_acls(permissions))
[docs]class Service(Resource):
"""
Resource of `service` type.
"""
[docs] __tablename__ = u"services"
[docs] resource_id = sa.Column(sa.Integer(),
sa.ForeignKey("resources.resource_id",
onupdate="CASCADE",
ondelete="CASCADE", ),
primary_key=True, )
[docs] resource_type_name = u"service"
[docs] __mapper_args__ = {u"polymorphic_identity": resource_type_name,
u"inherit_condition": resource_id == Resource.resource_id}
@property
[docs] def permissions(self):
raise TypeError("Service permissions must be accessed by 'magpie.services.ServiceInterface' "
"instead of 'magpie.models.Service'.")
@declared_attr
[docs] def url(self):
# http://localhost:8083
return sa.Column(sa.UnicodeText(), unique=True)
@declared_attr
[docs] def type(self):
"""
Identifier matching ``magpie.services.ServiceInterface.service_type``.
"""
# wps, wms, thredds,...
return sa.Column(sa.UnicodeText())
@declared_attr
[docs] def sync_type(self):
"""
Identifier matching ``magpie.helpers.SyncServiceInterface.sync_type``.
"""
# project-api, geoserver-api,...
return sa.Column(sa.UnicodeText(), nullable=True)
@staticmethod
[docs] def by_service_name(service_name, db_session):
db = get_db_session(db_session)
service = db.query(Service).filter(Resource.resource_name == service_name).first()
return service
[docs]class PathBase(object):
[docs] permissions = [
Permission.READ,
Permission.WRITE,
Permission.GET_CAPABILITIES,
Permission.GET_MAP,
Permission.GET_FEATURE_INFO,
Permission.GET_LEGEND_GRAPHIC,
Permission.GET_METADATA,
]
[docs]class File(Resource, PathBase):
[docs] child_resource_allowed = False
[docs] resource_type_name = u"file"
[docs] __mapper_args__ = {u"polymorphic_identity": resource_type_name}
[docs]class Directory(Resource, PathBase):
[docs] resource_type_name = u"directory"
[docs] __mapper_args__ = {u"polymorphic_identity": resource_type_name}
[docs]class Workspace(Resource):
[docs] resource_type_name = u"workspace"
[docs] __mapper_args__ = {u"polymorphic_identity": resource_type_name}
[docs] permissions = [
Permission.GET_CAPABILITIES,
Permission.GET_MAP,
Permission.GET_FEATURE_INFO,
Permission.GET_LEGEND_GRAPHIC,
Permission.GET_METADATA,
Permission.GET_FEATURE,
Permission.DESCRIBE_FEATURE_TYPE,
Permission.LOCK_FEATURE,
Permission.TRANSACTION,
]
[docs]class Route(Resource):
[docs] resource_type_name = u"route"
[docs] __mapper_args__ = {u"polymorphic_identity": resource_type_name}
[docs] permissions = [
Permission.READ, # access with inheritance (this route and all under it)
Permission.WRITE, # access with inheritance (this route and all under it)
Permission.READ_MATCH, # access without inheritance (only on this specific route)
Permission.WRITE_MATCH, # access without inheritance (only on this specific route)
]
[docs]class RemoteResource(BaseModel, Base):
[docs] __tablename__ = "remote_resources"
[docs] __possible_permissions__ = ()
[docs] _ziggurat_services = [ResourceTreeService]
[docs] resource_id = sa.Column(sa.Integer(), primary_key=True, nullable=False, autoincrement=True)
[docs] service_id = sa.Column(sa.Integer(),
sa.ForeignKey("services.resource_id",
onupdate="CASCADE",
ondelete="CASCADE"),
index=True,
nullable=False)
[docs] parent_id = sa.Column(sa.Integer(),
sa.ForeignKey("remote_resources.resource_id",
onupdate="CASCADE",
ondelete="SET NULL"),
nullable=True)
[docs] ordering = sa.Column(sa.Integer(), default=0, nullable=False)
[docs] resource_name = sa.Column(sa.Unicode(100), nullable=False)
[docs] resource_display_name = sa.Column(sa.Unicode(100), nullable=True)
[docs] resource_type = sa.Column(sa.Unicode(30), nullable=False)
[docs] def __repr__(self):
info = self.resource_type, self.resource_name, self.resource_id, self.ordering, self.parent_id
return "<RemoteResource: %s, %s, id: %s position: %s, parent_id: %s>" % info
[docs]class RemoteResourcesSyncInfo(BaseModel, Base):
[docs] __tablename__ = "remote_resources_sync_info"
[docs] id = sa.Column(sa.Integer(), primary_key=True, nullable=False, autoincrement=True)
[docs] service_id = sa.Column(sa.Integer(),
sa.ForeignKey("services.resource_id",
onupdate="CASCADE",
ondelete="CASCADE"),
index=True,
nullable=False)
[docs] service = relationship("Service", foreign_keys=[service_id])
[docs] remote_resource_id = sa.Column(sa.Integer(),
sa.ForeignKey("remote_resources.resource_id", onupdate="CASCADE",
ondelete="CASCADE"))
[docs] last_sync = sa.Column(sa.DateTime(), nullable=True)
@staticmethod
[docs] def by_service_id(service_id, session):
condition = RemoteResourcesSyncInfo.service_id == service_id
service_info = session.query(RemoteResourcesSyncInfo).filter(condition).first()
return service_info
[docs] def __repr__(self):
last_modified = self.last_sync.strftime("%Y-%m-%dT%H:%M:%S") if self.last_sync else None
info = self.service_id, last_modified, self.id
return "<RemoteResourcesSyncInfo service_id: %s, last_sync: %s, id: %s>" % info
[docs]class RemoteResourceTreeService(ResourceTreeService):
def __init__(self, service_cls):
self.model = RemoteResource
super(RemoteResourceTreeService, self).__init__(service_cls)
[docs]class RemoteResourceTreeServicePostgresSQL(ResourceTreeServicePostgreSQL):
"""
This is necessary, because ResourceTreeServicePostgresSQL.model is the Resource class. If we want to change it for a
RemoteResource, we need this class.
The ResourceTreeService.__init__ call sets the model.
"""
pass
ziggurat_model_init(User, Group, UserGroup, GroupPermission, UserPermission,
UserResourcePermission, GroupResourcePermission, Resource,
ExternalIdentity, passwordmanager=None)
[docs]resource_tree_service = ResourceTreeService(ResourceTreeServicePostgreSQL)
[docs]remote_resource_tree_service = RemoteResourceTreeService(RemoteResourceTreeServicePostgresSQL)
[docs]RESOURCE_TYPE_DICT = dict()
for res in [Service, Directory, File, Workspace, Route]:
if res.resource_type_name in RESOURCE_TYPE_DICT:
raise KeyError("Duplicate resource type identifiers not allowed")
RESOURCE_TYPE_DICT[res.resource_type_name] = res
[docs]def resource_factory(**kwargs):
resource_type = evaluate_call(lambda: kwargs["resource_type"], httpError=HTTPInternalServerError,
msgOnFail="kwargs do not contain required 'resource_type'",
content={u"kwargs": repr(kwargs)})
return evaluate_call(lambda: RESOURCE_TYPE_DICT[resource_type](**kwargs), httpError=HTTPInternalServerError,
msgOnFail="kwargs unpacking failed from specified 'resource_type' and 'RESOURCE_TYPE_DICT'",
content={u"kwargs": repr(kwargs), u"RESOURCE_TYPE_DICT": repr(RESOURCE_TYPE_DICT)})
[docs]def find_children_by_name(child_name, parent_id, db_session):
tree_struct = resource_tree_service.from_parent_deeper(parent_id=parent_id, limit_depth=1, db_session=db_session)
tree_level_entries = [node for node in tree_struct]
tree_level_filtered = [node.Resource for node in tree_level_entries if
node.Resource.resource_name.lower() == child_name.lower()]
return tree_level_filtered.pop() if len(tree_level_filtered) else None