def test_session_auth_token(): s = SessionFactory() # Calling environment should generate an auth token for the creator assert s.auth_token is None _ = s.environment assert s.auth_token.user == s.creator assert s.auth_token.expiry == s.expires_at + timedelta( minutes=WORKSTATIONS_GRACE_MINUTES) # old tokens should be deleted old_pk = s.auth_token.pk _ = s.environment assert s.auth_token.pk != old_pk # expiry should stay in sync s.maximum_duration = timedelta(days=1) s.save() assert s.auth_token.expiry == s.expires_at + timedelta( minutes=WORKSTATIONS_GRACE_MINUTES)
def test_password_change_clears_sessions(self): SessionFactory(user=self.user) SessionFactory(user=self.user) SessionFactory(user=self.user) assert_equal(3, Session.find().count()) self.user.set_password('killerqueen') assert_equal(0, Session.find().count())
def test_session_cleanup(http_image, docker_client, settings): path, sha256 = http_image wsi = WorkstationImageFactory(image__from_path=path, image_sha256=sha256, ready=True) # Execute the celery in place settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) try: s1, s2 = ( SessionFactory(workstation_image=wsi), SessionFactory(workstation_image=wsi, maximum_duration=timedelta(seconds=0)), ) assert s1.service.container assert s2.service.container stop_expired_services(app_label="workstations", model_name="session") assert s1.service.container with pytest.raises(NotFound): # noinspection PyStatementEffect s2.service.container finally: stop_all_sessions()
def test_session_api_patch_permissions(client, two_workstation_sets): tests = ( (two_workstation_sets.ws1.editor, 200, True), (two_workstation_sets.ws1.user, 200, True), (two_workstation_sets.ws1.user1, 404, False), (two_workstation_sets.ws2.editor, 404, False), (two_workstation_sets.ws2.user, 404, False), (UserFactory(), 404, False), (UserFactory(is_staff=True), 404, False), (None, 401, False), ) for test in tests: s = SessionFactory( workstation_image=two_workstation_sets.ws1.image, creator=two_workstation_sets.ws1.user, ) response = get_view_for_user( viewname="api:session-keep-alive", client=client, method=client.patch, user=test[0], reverse_kwargs={"pk": s.pk}, content_type="application/json", ) assert response.status_code == test[1] # The maximum duration should have changed from the default s.refresh_from_db() assert s.status == s.QUEUED # Read only, always unchanged assert (s.maximum_duration == timedelta(minutes=10)) is not test[2]
def test_correct_session_stopped(http_image, docker_client, settings): path, sha256 = http_image wsi = WorkstationImageFactory(image__from_path=path, image_sha256=sha256, ready=True) # Execute the celery in place settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) try: s1, s2 = ( SessionFactory(workstation_image=wsi), SessionFactory(workstation_image=wsi), ) assert s1.service.container assert s2.service.container s2.user_finished = True s2.save() assert s1.service.container with pytest.raises(NotFound): # noinspection PyStatementEffect s2.service.container finally: stop_all_sessions()
def expired_token(session): admin_user_id = 1 session_factory = SessionFactory() new_session = session_factory.new(admin_user_id, ttl=0) session.commit() token_factory = SessionTokenFactory() token = token_factory.new(new_session, test_config.CABINET_SECRET) yield token.decode("utf-8")
def test_session_cleanup(http_image, docker_client, settings): path, sha256 = http_image wsi = WorkstationImageFactory(image__from_path=path, image_sha256=sha256, ready=True) # Execute the celery in place settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) default_region = "eu-nl-1" try: with capture_on_commit_callbacks(execute=True): s1, s2, s3 = ( SessionFactory(workstation_image=wsi, region=default_region), SessionFactory( workstation_image=wsi, maximum_duration=timedelta(seconds=0), region=default_region, ), # An expired service in a different region SessionFactory( workstation_image=wsi, maximum_duration=timedelta(seconds=0), region="us-east-1", ), ) assert s1.service.container assert s2.service.container assert s3.service.container # Stop expired services in the default region stop_expired_services( app_label="workstations", model_name="session", region=default_region, ) assert s1.service.container with pytest.raises(NotFound): # noinspection PyStatementEffect s2.service.container assert s3.service.container finally: stop_all_sessions()
def test_workstation_ready(http_image, docker_client, settings): path, sha256 = http_image wsi = WorkstationImageFactory(image__from_path=path, image_sha256=sha256, ready=False) # Execute the celery in place settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) s = SessionFactory(workstation_image=wsi) s.refresh_from_db() assert s.status == s.FAILED
def test_workstation_ready(http_image, docker_client, settings): path, sha256 = http_image wsi = WorkstationImageFactory( image__from_path=path, image_sha256=sha256, ready=False ) # Execute the celery in place settings.task_eager_propagates = (True,) settings.task_always_eager = (True,) s = SessionFactory(workstation_image=wsi) s.refresh_from_db() assert s.status == s.FAILED
def test_session_detail(client): s1, s2 = SessionFactory(), SessionFactory() response = get_view_for_user( client=client, viewname="workstations:session-detail", reverse_kwargs={ "slug": s1.workstation_image.workstation.slug, "pk": s1.pk, }, user=s1.creator, ) assert response.status_code == 200 assert str(s1.pk) in response.rendered_content assert str(s2.pk) not in response.rendered_content
def test_workstations_staff_views(client, view): if view in [ "workstations:update", "workstations:detail", "workstations:image-create", "workstations:session-create", ]: reverse_kwargs = {"slug": WorkstationFactory().slug} elif view in ["workstations:image-detail", "workstations:image-update"]: wsi = WorkstationImageFactory() reverse_kwargs = {"slug": wsi.workstation.slug, "pk": wsi.pk} elif view in [ "workstations:session-detail", "workstations:session-update", ]: session = SessionFactory() reverse_kwargs = { "slug": session.workstation_image.workstation.slug, "pk": session.pk, } else: reverse_kwargs = {} validate_staff_only_view( client=client, viewname=view, reverse_kwargs=reverse_kwargs )
def test_session_proxy_permissions(client, two_workstation_sets): tests = ( (two_workstation_sets.ws1.editor, 403), (two_workstation_sets.ws1.user, 200), (two_workstation_sets.ws1.user1, 403), (two_workstation_sets.ws2.editor, 403), (two_workstation_sets.ws2.user, 403), (UserFactory(), 403), (UserFactory(is_staff=True), 403), (None, 403), ) s = SessionFactory( workstation_image=two_workstation_sets.ws1.image, creator=two_workstation_sets.ws1.user, ) for test in tests: response = get_view_for_user( viewname="session-proxy", client=client, user=test[0], reverse_kwargs={ "slug": s.workstation_image.workstation.slug, "pk": s.pk, "path": "foo/bar/../../baz", "rendering_subdomain": s.region, }, ) assert response.status_code == test[1]
def test_workstation_user_permissions(client, two_workstation_sets, viewname): tests = ( (two_workstation_sets.ws1.editor, 200), (two_workstation_sets.ws1.user, 200), (two_workstation_sets.ws2.editor, 403), (two_workstation_sets.ws2.user, 403), (UserFactory(), 403), (UserFactory(is_staff=True), 403), (None, 302), ) two_workstation_sets.ws1.image.ready = True two_workstation_sets.ws1.image.save() kwargs = {"slug": two_workstation_sets.ws1.workstation.slug} if viewname == "session-detail": s = SessionFactory( workstation_image=two_workstation_sets.ws1.image, creator=two_workstation_sets.ws1.user, ) kwargs.update({"pk": s.pk, "rendering_subdomain": s.region}) tests += ((two_workstation_sets.ws1.user1, 403), ) for test in tests: response = get_view_for_user( viewname=viewname, client=client, user=test[0], reverse_kwargs=kwargs, ) assert response.status_code == test[1]
def test_workstation_proxy(client): u1, u2 = UserFactory(), UserFactory() session = SessionFactory(creator=u1) url = reverse( "workstations:session-proxy", kwargs={ "slug": session.workstation_image.workstation.slug, "pk": session.pk, "path": "foo/bar/../baz/test", }, ) response = get_view_for_user(client=client, url=url, user=u1) assert response.status_code == 200 assert response.has_header("X-Accel-Redirect") redirect_url = response.get("X-Accel-Redirect") assert redirect_url.endswith("foo/baz/test") assert redirect_url.startswith("/workstation-proxy/") assert session.hostname in redirect_url # try as another user response = get_view_for_user(client=client, url=url, user=u2) assert not response.has_header("X-Accel-Redirect") assert response.status_code == 403
def test_session_detail_api(client): user = UserFactory(is_staff=True) s = SessionFactory() response = get_view_for_user( client=client, viewname="api:session-detail", reverse_kwargs={"pk": s.pk}, user=user, content_type="application/json", ) # Status and pk are required by the js app assert response.status_code == 200 assert all([k in response.json() for k in ["pk", "status"]]) assert response.json()["pk"] == str(s.pk) assert response.json()["status"] == s.get_status_display()
def test_remove_session_for_user(self): SessionFactory(user=self.user) # sanity check assert_equal(1, Session.find().count()) utils.remove_sessions_for_user(self.user) assert_equal(0, Session.find().count()) SessionFactory() SessionFactory(user=self.user) # sanity check assert_equal(2, Session.find().count()) utils.remove_sessions_for_user(self.user) assert_equal(1, Session.find().count())
def test_session_detail_api(client): user = UserFactory() s = SessionFactory(creator=user) response = get_view_for_user( client=client, viewname="api:session-detail", reverse_kwargs={"pk": s.pk}, user=user, content_type="application/json", ) # Status and pk are required by the js app assert response.status_code == 200 assert all([k in response.json() for k in ["pk", "status"]]) assert response.json()["pk"] == str(s.pk) assert response.json()["status"] == s.get_status_display()
def test_session_update_read_only_fails(client): user = UserFactory() s = SessionFactory(creator=user) response = get_view_for_user( client=client, method=client.patch, viewname="api:session-keep-alive", reverse_kwargs={"pk": s.pk}, user=user, data={"status": "Stopped"}, content_type="application/json", ) assert response.status_code == 200 s.refresh_from_db() assert s.status == s.QUEUED
def test_expired_cookie(self): self.session = SessionFactory( user=self.user1, date_created=(datetime.utcnow() - timedelta(seconds=settings.OSF_SESSION_TIMEOUT))) cookie = self.user1.get_or_create_cookie() self.app.set_cookie(settings.COOKIE_NAME, str(cookie)) res = self.app.get(self.reachable_url) assert_equal(res.status_code, 302) assert_in('login', res.location)
def test_session_update_extends_timeout(client): user = UserFactory() s = SessionFactory(creator=user) assert s.maximum_duration == timedelta(minutes=10) response = get_view_for_user( client=client, method=client.patch, viewname="api:session-keep-alive", reverse_kwargs={"pk": s.pk}, user=user, content_type="application/json", ) assert response.status_code == 200 s.refresh_from_db() # Just check that it changed from the default assert s.maximum_duration != timedelta(minutes=10)
def test_correct_session_stopped(http_image, docker_client, settings): path, sha256 = http_image wsi = WorkstationImageFactory(image__from_path=path, image_sha256=sha256, ready=True) # Execute the celery in place settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) try: with capture_on_commit_callbacks(execute=True): s1, s2 = ( SessionFactory(workstation_image=wsi), SessionFactory(workstation_image=wsi), ) assert s1.service.container assert s2.service.container s2.refresh_from_db() auth_token_pk = s2.auth_token.pk with capture_on_commit_callbacks(execute=True): s2.user_finished = True s2.save() assert s1.service.container with pytest.raises(NotFound): # noinspection PyStatementEffect s2.service.container with pytest.raises(ObjectDoesNotExist): # auth token should be deleted when the service is stopped AuthToken.objects.get(pk=auth_token_pk) finally: stop_all_sessions()
def test_session_start(http_image, docker_client, settings): path, sha256 = http_image wsi = WorkstationImageFactory(image__from_path=path, image_sha256=sha256, ready=True) # Execute the celery in place settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) with capture_on_commit_callbacks(execute=True): s = SessionFactory(workstation_image=wsi) try: assert s.service.container s.refresh_from_db() assert s.status == s.STARTED container = s.service.container assert container.labels["traefik.enable"] == "true" assert container.labels[ f"traefik.http.services.{s.hostname}-http.loadbalancer.server.port"] == str( s.workstation_image.http_port) assert container.labels[ f"traefik.http.services.{s.hostname}-websocket.loadbalancer.server.port"] == str( s.workstation_image.websocket_port) networks = container.attrs.get("NetworkSettings")["Networks"] assert len(networks) == 1 assert settings.WORKSTATIONS_NETWORK_NAME in networks with capture_on_commit_callbacks(execute=True): s.user_finished = True s.save() with pytest.raises(NotFound): # noinspection PyStatementEffect s.service.container finally: s.stop()
def tokens(session, user) -> dict: admin_user_id = 1 tokens_to_return = {} session_factory = SessionFactory() privileged_session = session_factory.new(admin_user_id) expired_session = session_factory.new(admin_user_id, ttl=0) unprivileged_session = session_factory.new(user.id) session.commit() token_factory = SessionTokenFactory() privileged_token = token_factory.new(privileged_session, test_config.CABINET_SECRET) expired_token = token_factory.new(expired_session, test_config.CABINET_SECRET) unprivileged_token = token_factory.new( unprivileged_session, test_config.CABINET_SECRET ) tokens_to_return["privileged"] = privileged_token.decode("utf-8") tokens_to_return["expired"] = expired_token.decode("utf-8") tokens_to_return["unprivileged"] = unprivileged_token.decode("utf-8") yield tokens_to_return
def test_session_update(client): session = SessionFactory() assert session.user_finished == False response = get_view_for_user( client=client, method=client.post, viewname="workstations:session-update", reverse_kwargs={ "slug": session.workstation_image.workstation.slug, "pk": session.pk, }, user=session.creator, data={"user_finished": True}, ) assert response.status_code == 302 assert response.url == session.get_absolute_url() session.refresh_from_db() assert session.user_finished == True
def test_session_list_api(client): user = UserFactory() response = get_view_for_user( client=client, viewname="api:session-list", user=user, content_type="application/json", ) assert response.status_code == 200 assert response.json()["count"] == 0 s, _ = SessionFactory(creator=user), SessionFactory(creator=user) response = get_view_for_user( client=client, viewname="api:session-list", user=user, content_type="application/json", ) assert response.status_code == 200 assert response.json()["count"] == 2
def test_session_environ(settings, debug): settings.DEBUG = debug s = SessionFactory() env = s.environment assert env["GRAND_CHALLENGE_API_ROOT"] == "https://testserver/api/v1/" assert "Bearer " in env["GRAND_CHALLENGE_AUTHORIZATION"] assert env["WORKSTATION_SESSION_ID"] == str(s.pk) assert "WORKSTATION_SENTRY_DSN" in env if debug: assert "GRAND_CHALLENGE_UNSAFE" in env else: assert "GRAND_CHALLENGE_UNSAFE" not in env
def test_session_update(client): user = UserFactory(is_staff=True) session = SessionFactory() assert session.user_finished == False response = get_view_for_user( client=client, method=client.post, viewname="workstations:session-update", reverse_kwargs={ "slug": session.workstation_image.workstation.slug, "pk": session.pk, }, user=user, data={"user_finished": True}, ) assert response.status_code == 302 assert response.url == session.get_absolute_url() session.refresh_from_db() assert session.user_finished == True
def test_session_only_patchable_by_creator(client): user = UserFactory() s = SessionFactory(creator=user) assert s.maximum_duration == timedelta(minutes=10) response = get_view_for_user( client=client, method=client.patch, viewname="api:session-keep-alive", reverse_kwargs={"pk": s.pk}, user=UserFactory(), content_type="application/json", ) assert response.status_code == 404
def test_session_environ(settings, debug): settings.DEBUG = debug s = SessionFactory() env = s.environment assert env["GRAND_CHALLENGE_PROXY_URL_MAPPINGS"] == "" assert "{key}" in env["GRAND_CHALLENGE_QUERY_IMAGE_URL"] assert env["GRAND_CHALLENGE_QUERY_IMAGE_URL"].startswith( "https://testserver") assert (Token.objects.get(user=s.creator).key in env["GRAND_CHALLENGE_AUTHORIZATION"]) if debug: assert "GRAND_CHALLENGE_UNSAFE" in env else: assert "GRAND_CHALLENGE_UNSAFE" not in env
def test_workstation_url_session(settings): image, overlay = ImageFactory(), ImageFactory() context = {} url = workstation_url(context=context, image=image, overlay=overlay) assert url.startswith(settings.WORKSTATIONS_GLOBAL_APPLICATION) assert f"{settings.WORKSTATIONS_BASE_IMAGE_QUERY_PARAM}={image.pk}" in url assert f"{settings.WORKSTATIONS_OVERLAY_QUERY_PARAM}={overlay.pk}" in url session = SessionFactory() # This will be set by # grandchallenge.workstations.context_processors.workstation_session context = {"workstation_session": session} url = workstation_url(context=context, image=image, overlay=overlay) assert url.startswith(session.workstation_url) assert f"{settings.WORKSTATIONS_BASE_IMAGE_QUERY_PARAM}={image.pk}" in url assert f"{settings.WORKSTATIONS_OVERLAY_QUERY_PARAM}={overlay.pk}" in url
def test_session_start(http_image, docker_client, settings): path, sha256 = http_image wsi = WorkstationImageFactory( image__from_path=path, image_sha256=sha256, ready=True ) # Execute the celery in place settings.task_eager_propagates = (True,) settings.task_always_eager = (True,) s = SessionFactory(workstation_image=wsi) try: assert s.service.container s.refresh_from_db() assert s.status == s.STARTED container = s.service.container assert container.labels["traefik.enable"] == "true" assert container.labels["traefik.http.port"] == str( s.workstation_image.http_port ) assert container.labels["traefik.websocket.port"] == str( s.workstation_image.websocket_port ) networks = container.attrs.get("NetworkSettings")["Networks"] assert len(networks) == 1 assert settings.WORKSTATIONS_NETWORK_NAME in networks s.user_finished = True s.save() with pytest.raises(NotFound): # noinspection PyStatementEffect s.service.container finally: s.stop()
def test_session_auth_token(): s = SessionFactory() # Calling environment should generate an auth token for the creator assert s.auth_token is None _ = s.environment expected_duration = ( s.created + timedelta(minutes=settings.WORKSTATIONS_GRACE_MINUTES) + timedelta(seconds=settings.WORKSTATIONS_SESSION_DURATION_LIMIT)) assert s.auth_token.user == s.creator assert abs(s.auth_token.expiry - expected_duration) < timedelta(seconds=10) # old tokens should be deleted old_pk = s.auth_token.pk _ = s.environment assert s.auth_token.pk != old_pk
def test_session_api_permissions(client): tests = [(UserFactory(), 403), (UserFactory(is_staff=True), 200)] session = SessionFactory() for test in tests: response = get_view_for_user( client=client, viewname="api:session-list", user=test[0], content_type="application/json", ) assert response.status_code == test[1] response = get_view_for_user( client=client, viewname="api:session-detail", reverse_kwargs={"pk": session.pk}, user=test[0], content_type="application/json", ) assert response.status_code == test[1]
def test_session_api_permissions(client, two_workstation_sets, viewname): tests = ( (two_workstation_sets.ws1.editor, 200), (two_workstation_sets.ws1.user, 200), (two_workstation_sets.ws1.user1, 404), (two_workstation_sets.ws2.editor, 404), (two_workstation_sets.ws2.user, 404), (UserFactory(), 404), (UserFactory(is_staff=True), 404), (None, 404), ) s = SessionFactory( workstation_image=two_workstation_sets.ws1.image, creator=two_workstation_sets.ws1.user, ) if viewname == "api:session-detail": kwargs = {"pk": s.pk} else: kwargs = {} for test in tests: response = get_view_for_user( viewname=viewname, client=client, user=test[0], reverse_kwargs=kwargs, ) if viewname == "api:session-list": if test[1] == 200: assert response.json()["count"] == 1 else: assert response.json()["count"] == 0 else: assert response.status_code == test[1]
def test_session_limit(http_image, docker_client, settings): path, sha256 = http_image wsi = WorkstationImageFactory( image__from_path=path, image_sha256=sha256, ready=True ) # Execute the celery in place settings.task_eager_propagates = (True,) settings.task_always_eager = (True,) settings.WORKSTATIONS_MAXIMUM_SESSIONS = 1 try: s1 = SessionFactory(workstation_image=wsi) s1.refresh_from_db() assert s1.status == s1.STARTED s2 = SessionFactory(workstation_image=wsi) s2.refresh_from_db() assert s2.status == s2.FAILED s1.stop() s3 = SessionFactory(workstation_image=wsi) s3.refresh_from_db() assert s3.status == s3.STARTED finally: stop_all_sessions()