def db_services(registry_services: List[ServiceOut],
                user_groups: List[GroupAtDB]) -> List[ServiceAccessRightsAtDB]:
    return [
        ServiceAccessRightsAtDB(
            key=s.key,
            version=s.version,
            gid=user_groups[0].gid,
            execute_access=True,
            product_name="osparc",
        ) for s in registry_services
    ]
예제 #2
0
async def evaluate_default_policy(
    app: FastAPI, service: ServiceDockerData
) -> Tuple[Optional[PositiveInt], List[ServiceAccessRightsAtDB]]:
    """Given a service, it returns the owner's group-id (gid) and a list of access rights following
    default access-rights policies

    - DEFAULT Access Rights policies:
        1. All services published in osparc prior 19.08.2020 will be visible to everyone (refered as 'old service').
        2. Services published after 19.08.2020 will be visible ONLY to his/her owner
        3. Front-end services are have execute-access to everyone
    """
    db_engine: AsyncEngine = app.state.engine

    groups_repo = GroupsRepository(db_engine)
    owner_gid = None
    group_ids: List[PositiveInt] = []

    if _is_frontend_service(service) or await _is_old_service(app, service):
        everyone_gid = (await groups_repo.get_everyone_group()).gid
        logger.debug("service %s:%s is old or frontend", service.key,
                     service.version)
        # let's make that one available to everyone
        group_ids.append(everyone_gid)

    # try to find the owner
    possible_owner_email = [service.contact
                            ] + [author.email for author in service.authors]

    for user_email in possible_owner_email:
        possible_gid = await groups_repo.get_user_gid_from_email(user_email)
        if possible_gid:
            if not owner_gid:
                owner_gid = possible_gid
    if not owner_gid:
        logger.warning("service %s:%s has no owner", service.key,
                       service.version)
    else:
        group_ids.append(owner_gid)

    # we add the owner with full rights, unless it's everyone
    default_access_rights = [
        ServiceAccessRightsAtDB(
            key=service.key,
            version=service.version,
            gid=gid,
            execute_access=True,
            write_access=(gid == owner_gid),
            product_name=app.state.settings.
            CATALOG_ACCESS_RIGHTS_DEFAULT_PRODUCT_NAME,
        ) for gid in set(group_ids)
    ]

    return (owner_gid, default_access_rights)
def test_reduce_access_rights():
    sample = ServiceAccessRightsAtDB.parse_obj({
        "key": "simcore/services/dynamic/sim4life",
        "version": "1.0.9",
        "gid": 8,
        "execute_access": True,
        "write_access": True,
        "product_name": "osparc",
    })

    # fixture with overrides and with other products
    reduced = reduce_access_rights([
        sample.copy(deep=True),
        sample.copy(deep=True),
        sample.copy(update={"execute_access": False}, deep=True),
        sample.copy(update={"product_name": "s4l"}, deep=True),
    ])

    # two products with the same flags
    assert len(reduced) == 2
    assert reduced[0].dict(include={"execute_access", "write_access"}) == {
        "execute_access": True,
        "write_access": True,
    }
    assert reduced[1].dict(include={"execute_access", "write_access"}) == {
        "execute_access": True,
        "write_access": True,
    }

    # two gids with the different falgs
    reduced = reduce_access_rights([
        sample.copy(deep=True),
        sample.copy(
            update={
                "gid": 1,
                "execute_access": True,
                "write_access": False
            },
            deep=True,
        ),
    ])

    assert len(reduced) == 2
    assert reduced[0].dict(include={"execute_access", "write_access"}) == {
        "execute_access": True,
        "write_access": True,
    }
    assert reduced[1].dict(include={"execute_access", "write_access"}) == {
        "execute_access": True,
        "write_access": False,
    }
예제 #4
0
def reduce_access_rights(
    access_rights: List[ServiceAccessRightsAtDB],
    reduce_operation: Callable = operator.ior,
) -> List[ServiceAccessRightsAtDB]:
    """
    Reduces a list of access-rights per target
    By default, the reduction is OR (i.e. preserves True flags)
    """

    # TODO: probably a lot of room to optimize
    # helper functions to simplify operation of access rights

    def get_target(
            access: ServiceAccessRightsAtDB) -> Tuple[Union[str, int], ...]:
        """Hashable identifier of the resource the access rights apply to"""
        return tuple(
            [access.key, access.version, access.gid, access.product_name])

    def get_flags(access: ServiceAccessRightsAtDB) -> Dict[str, bool]:
        """Extracts only"""
        return access.dict(include={"execute_access", "write_access"})

    access_flags_map = {}
    for access in access_rights:
        target = get_target(access)
        access_flags = access_flags_map.get(target)

        if access_flags:
            # applies reduction on flags
            for key, value in get_flags(access).items():
                access_flags[key] = reduce_operation(access_flags[key],
                                                     value)  # a |= b
        else:
            access_flags_map[target] = get_flags(access)

    reduced_access_rights = []
    for target in access_flags_map:
        reduced_access_rights.append(
            ServiceAccessRightsAtDB(
                key=target[0],
                version=target[1],
                gid=target[2],
                product_name=target[3],
                **access_flags_map[target],
            ))

    return reduced_access_rights
async def test_create_services(services_repo: ServicesRepository,
                               service_catalog_faker: Callable):
    # creates fake data
    fake_service, *fake_access_rights = service_catalog_faker(
        "simcore/services/dynamic/jupyterlab",
        "1.0.0",
        team_access=None,
        everyone_access=None,
    )

    # validation
    service = ServiceMetaDataAtDB.parse_obj(fake_service)
    service_access_rights = [
        ServiceAccessRightsAtDB.parse_obj(a) for a in fake_access_rights
    ]

    new_service = await services_repo.create_service(service,
                                                     service_access_rights)

    assert new_service.dict(include=set(fake_service.keys())) == service.dict()
예제 #6
0
async def _ensure_published_templates_accessible(
    db_engine: Engine, default_product_name: str
) -> None:
    # Rationale: if a project template was published, its services must be available to everyone.
    # a published template has a column Published that is set to True
    projects_repo = ProjectsRepository(db_engine)
    published_services: Set[Tuple[str, str]] = {
        (service.key, service.version)
        for service in await projects_repo.list_services_from_published_templates()
    }

    groups_repo = GroupsRepository(db_engine)
    everyone_gid = (await groups_repo.get_everyone_group()).gid

    services_repo = ServicesRepository(db_engine)
    available_services: Set[Tuple[str, str]] = {
        (service.key, service.version)
        for service in await services_repo.list_services(
            gids=[everyone_gid], execute_access=True
        )
    }

    missing_services = published_services - available_services
    missing_services_access_rights = [
        ServiceAccessRightsAtDB(
            key=service[0],
            version=service[1],
            gid=everyone_gid,
            execute_access=True,
            product_name=default_product_name,
        )
        for service in missing_services
    ]
    if missing_services_access_rights:
        logger.info(
            "Adding access rights for published templates\n: %s",
            missing_services_access_rights,
        )
        await services_repo.upsert_service_access_rights(missing_services_access_rights)
예제 #7
0
 async def list_services_access_rights(
     self, key_versions: List[Tuple[str, str]], product_name: Optional[str] = None
 ) -> Dict[Tuple[str, str], List[ServiceAccessRightsAtDB]]:
     """Batch version of get_service_access_rights"""
     service_to_access_rights = defaultdict(list)
     query = sa.select([services_access_rights]).where(
         tuple_(services_access_rights.c.key, services_access_rights.c.version).in_(
             key_versions
         )
         & (services_access_rights.c.product_name == product_name)
         if product_name
         else True
     )
     async with self.db_engine.acquire() as conn:
         async for row in conn.execute(query):
             service_to_access_rights[
                 (
                     row[services_access_rights.c.key],
                     row[services_access_rights.c.version],
                 )
             ].append(ServiceAccessRightsAtDB(**row))
     return service_to_access_rights
예제 #8
0
    async def get_service_access_rights(
        self,
        key: str,
        version: str,
        product_name: Optional[str] = None,
    ) -> List[ServiceAccessRightsAtDB]:
        """
        - If product_name is not specificed, then all are considered in the query
        """
        services_in_db = []
        search_expression = (services_access_rights.c.key == key) & (
            services_access_rights.c.version == version
        )
        if product_name:
            search_expression &= services_access_rights.c.product_name == product_name

        query = sa.select([services_access_rights]).where(search_expression)

        async with self.db_engine.acquire() as conn:
            async for row in conn.execute(query):
                services_in_db.append(ServiceAccessRightsAtDB(**row))
        return services_in_db
예제 #9
0
async def modify_service(
        # pylint: disable=too-many-arguments
        user_id: int,
        service_key: constr(regex=KEY_RE),
        service_version: constr(regex=VERSION_RE),
        updated_service: ServiceUpdate,
        director_client: DirectorApi = Depends(get_director_api),
        groups_repository: GroupsRepository = Depends(
            get_repository(GroupsRepository)),
        services_repo: ServicesRepository = Depends(
            get_repository(ServicesRepository)),
        x_simcore_products_name: str = Header(None),
):
    if is_function_service(service_key):
        # NOTE: this is a temporary decision after discussing with OM
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Cannot update front-end services",
        )

    # check the service exists
    await director_client.get(
        f"/services/{urllib.parse.quote_plus(service_key)}/{service_version}")
    # the director client already raises an exception if not found

    # get the user groups
    user_groups = await groups_repository.list_user_groups(user_id)
    if not user_groups:
        # deny access
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You have unsufficient rights to access the service",
        )
    # check the user has write access to this service
    writable_service = await services_repo.get_service(
        service_key,
        service_version,
        gids=[group.gid for group in user_groups],
        write_access=True,
        product_name=x_simcore_products_name,
    )
    if not writable_service:
        # deny access
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You have unsufficient rights to modify the service",
        )

    # let's modify the service then
    await services_repo.update_service(
        ServiceMetaDataAtDB(
            key=service_key,
            version=service_version,
            **updated_service.dict(exclude_unset=True),
        ))
    # let's modify the service access rights (they can be added/removed/modified)
    current_gids_in_db = [
        r.gid for r in await services_repo.get_service_access_rights(
            service_key, service_version, product_name=x_simcore_products_name)
    ]

    if updated_service.access_rights:
        # start by updating/inserting new entries
        new_access_rights = [
            ServiceAccessRightsAtDB(
                key=service_key,
                version=service_version,
                gid=gid,
                execute_access=rights.execute_access,
                write_access=rights.write_access,
                product_name=x_simcore_products_name,
            ) for gid, rights in updated_service.access_rights.items()
        ]
        await services_repo.upsert_service_access_rights(new_access_rights)

        # then delete the ones that were removed
        removed_gids = [
            gid for gid in current_gids_in_db
            if gid not in updated_service.access_rights
        ]
        deleted_access_rights = [
            ServiceAccessRightsAtDB(
                key=service_key,
                version=service_version,
                gid=gid,
                product_name=x_simcore_products_name,
            ) for gid in removed_gids
        ]
        await services_repo.delete_service_access_rights(deleted_access_rights)

    # now return the service
    return await get_service(
        user_id,
        service_key,
        service_version,
        director_client,
        groups_repository,
        services_repo,
        x_simcore_products_name,
    )
예제 #10
0
 def get_flags(access: ServiceAccessRightsAtDB) -> Dict[str, bool]:
     """Extracts only"""
     return access.dict(include={"execute_access", "write_access"})