diff --git a/addons/base/utils.py b/addons/base/utils.py index 92be45b3739..513493ec36f 100644 --- a/addons/base/utils.py +++ b/addons/base/utils.py @@ -54,3 +54,37 @@ def format_last_known_metadata(auth, node, file, error_type): ] return ''.join(parts) return msg + + +class GravyValetAddonAppConfig: + class MockNodeSetting: + def __init__(self, resource, request, legacy_config): + ... + + class MockUserSetting: + def __init__(self, resource, request, legacy_config): + ... + + def __init__(self, gravyvalet_data, resource, auth): + self.gravyvalet_data = gravyvalet_data + + # TODO: Names in GV must be exact matches? + self.legacy_config = settings.ADDONS_AVAILABLE_DICT[self.gravyvalet_data['data']['attributes']['name']] + self.resource = resource + self.auth = auth + self.FOLDER_SELECTED = self.legacy_config.FOLDER_SELECTED + self.NODE_AUTHORIZED = self.legacy_config.NODE_DEAUTHORIZED + self.NODE_DEAUTHORIZED = self.legacy_config.NODE_DEAUTHORIZED + self.actions = self.legacy_config.actions + + @property + def node_settings(self): + return self.MockNodeSetting(self.resource, self.auth, self.legacy_config) + + @property + def user_settings(self): + return self.MockUserSetting(self.resource, self.auth, self.legacy_config) + + @property + def configured(self): + return self.legacy_config.configured diff --git a/api_tests/providers/test_files_with_gv.py b/api_tests/providers/test_files_with_gv.py new file mode 100644 index 00000000000..2eee014f3da --- /dev/null +++ b/api_tests/providers/test_files_with_gv.py @@ -0,0 +1,154 @@ +import pytest + +from osf import features +from api.base.settings.defaults import API_BASE +from api_tests import utils as api_utils +from osf_tests.factories import ( + AuthUserFactory, + ProjectFactory, +) +from waffle.testutils import override_flag +from django.shortcuts import reverse + + +@pytest.mark.django_db +class TestWaffleFilesProviderView: + + @pytest.fixture(autouse=True) + def override_flag(self): + with override_flag(features.ENABLE_GV, active=True): + yield + + @pytest.fixture() + def user(self): + return AuthUserFactory() + + @pytest.fixture() + def provider_gv_id(self): + return 1337 + + @pytest.fixture() + def node(self, user): + return ProjectFactory( + creator=user, + comment_level='public' + ) + + @pytest.fixture() + def file(self, user, node): + return api_utils.create_test_file( + node, + user, + create_guid=False + ) + + @pytest.fixture() + def addon_files_url(self, node, provider_gv_id): + return reverse( + 'nodes:node-storage-provider-detail', + kwargs={ + 'version': 'v2', + 'node_id': node._id, + 'provider': provider_gv_id + } + ) + + def test_must_have_auth(self, app, addon_files_url): + res = app.get( + addon_files_url, + expect_errors=True + ) + assert res.status_code == 401 + + def test_must_be_contributor(self, app, addon_files_url): + res = app.get( + addon_files_url, + auth=AuthUserFactory().auth, + expect_errors=True + ) + assert res.status_code == 403 + + def test_get_file_provider(self, app, user, addon_files_url, file, provider_gv_id): + res = app.get( + addon_files_url, + auth=user.auth + ) + + assert res.status_code == 200 + attributes = res.json['data']['attributes'] + assert attributes['provider'] == str(provider_gv_id) + assert attributes['name'] == str(provider_gv_id) + assert res.json['data']['id'] == f'{file.target._id}:{str(provider_gv_id)}' + + +@pytest.mark.django_db +class TestWaffleFilesView: + + @pytest.fixture(autouse=True) + def override_flag(self): + with override_flag(features.ENABLE_GV, active=True): + yield + + @pytest.fixture() + def user(self): + return AuthUserFactory() + + @pytest.fixture() + def provider_gv_id(self): + return 1 + + @pytest.fixture() + def node(self, user): + return ProjectFactory( + creator=user, + comment_level='public' + ) + + @pytest.fixture() + def file(self, user, node): + return api_utils.create_test_file( + node, + user, + create_guid=False + ) + + @pytest.fixture() + def file_url(self, node, provider_gv_id): + return reverse( + 'nodes:node-files', + kwargs={ + 'version': 'v2', + 'node_id': node._id, + 'provider': str(provider_gv_id) + '/', + 'path': 'test_path/' + } + ) + + def test_must_have_auth(self, app, file_url): + res = app.get( + file_url, + expect_errors=True + ) + assert res.status_code == 401 + + def test_must_be_contributor(self, app, file_url): + res = app.get( + file_url, + auth=AuthUserFactory().auth, + expect_errors=True + ) + assert res.status_code == 403 + + def test_get_file_provider(self, app, user, file_url, file, provider_gv_id): + res = app.get( + file_url, + auth=user.auth + ) + + assert res.status_code == 200 + attributes = res.json['data']['attributes'] + assert attributes['provider'] == str(provider_gv_id) + assert attributes['name'] == str(provider_gv_id) + assert res.json['data']['id'] == f'{file.target._id}:{str(provider_gv_id)}' + + diff --git a/osf/models/node.py b/osf/models/node.py index 0869e019ff2..f5a0a55c8f8 100644 --- a/osf/models/node.py +++ b/osf/models/node.py @@ -3,6 +3,8 @@ import itertools import logging import re +import waffle +import requests from future.moves.urllib.parse import urljoin import warnings from rest_framework import status as http_status @@ -54,6 +56,7 @@ from .user import OSFUser from .validators import validate_title, validate_doi from framework.auth.core import Auth +from osf import features from osf.utils.datetime_aware_jsonfield import DateTimeAwareJSONField from osf.utils.fields import NonNaiveDateTimeField, ensure_str from osf.utils.requests import get_request_and_user_id, string_type_request_headers @@ -83,7 +86,7 @@ from api.caching import settings as cache_settings from api.caching.utils import storage_usage_cache from api.share.utils import update_share - +from addons.base.utils import GravyValetAddonAppConfig logger = logging.getLogger(__name__) @@ -2430,6 +2433,28 @@ def _remove_from_associated_collections(self, auth=None, force=False): force=True ) + def get_addon(self, name, is_deleted=False): + request, user_id = get_request_and_user_id() + + default_addons = ['wiki'] + for addon in settings.ADDONS_AVAILABLE: + if 'node' in addon.added_default: + default_addons.append(addon.short_name) + + if waffle.flag_is_active(request, features.ENABLE_GV) and name not in ['osfstorage', 'wiki']: + resp = requests.get( + settings.GV_EXTERNAL_STORAGE_ENDPOINT.format(service_id=name), + auth=(request.user.username, request.user.password) + ) + data = resp.json() + return GravyValetAddonAppConfig( + data, + self, + auth=(request.user.username, request.user.password) + ) + else: + return super().get_addon(name, is_deleted) + class NodeUserObjectPermission(UserObjectPermissionBase): """ diff --git a/osf/models/user.py b/osf/models/user.py index d9d7d5db68e..19d7fa4ae0d 100644 --- a/osf/models/user.py +++ b/osf/models/user.py @@ -3,6 +3,7 @@ import re from future.moves.urllib.parse import urljoin, urlencode import uuid +import waffle from copy import deepcopy from flask import Request as FlaskRequest @@ -59,6 +60,10 @@ from website.util.metrics import OsfSourceTags from importlib import import_module from osf.utils.requests import get_headers_from_request +import requests +from osf.utils.requests import get_request_and_user_id +from osf import features +from addons.base.utils import GravyValetAddonAppConfig SessionStore = import_module(settings.SESSION_ENGINE).SessionStore @@ -1220,6 +1225,25 @@ def create_unregistered(cls, fullname, email=None): return user + def get_addon(self, name, is_deleted=False, auth=None): + request, user_id = get_request_and_user_id() + + default_addons = ['wiki'] + for addon in website_settings.ADDONS_AVAILABLE: + if 'user' in addon.added_default: + default_addons.append(addon.short_name) + + if waffle.flag_is_active(request, features.ENABLE_GV) and name not in ['osfstorage', 'wiki']: + resp = requests.get( + website_settings.GV_USER_ADDON_ENDPOINT.format(account_id=name), + auth=auth + ) + resp.raise_for_status() + data = resp.json()['data'] + return GravyValetAddonAppConfig(data, self, auth) + else: + return super().get_addon(name, is_deleted) + def update_guessed_names(self): """Updates the CSL name fields inferred from the the full name. """ diff --git a/website/settings/defaults.py b/website/settings/defaults.py index ce44070357b..a83903f267f 100644 --- a/website/settings/defaults.py +++ b/website/settings/defaults.py @@ -2142,3 +2142,12 @@ def from_node_usage(cls, usage_bytes, private_limit=None, public_limit=None): WAFFLE_VALUES_YAML = 'osf/features.yaml' DEFAULT_DRAFT_NODE_TITLE = 'Untitled' +GV_RESOURCE_DOMAIN = 'http://192.168.168.167:8004/v1/resource-references/?filter[resource_uri]={owner_uri}' +GV_USER_DOMAIN = 'http://192.168.168.167:8004/v1/user-references/?filter[user_uri]={owner_uri}' +GV_API_ROOT = 'http://192.168.168.167:8004/v1' +GV_RESOURCE_ENDPOINT = GV_API_ROOT + 'resource-references/?filter[resource_uri]={resource_uri}' +GV_USER_ENDPOINT = GV_API_ROOT + 'user-references/?filter[user_uri]={owner_uri}' +# These two are for `get_addon` vs `get_addons` +GV_USER_ADDON_ENDPOINT = 'http://192.168.168.167:8004/v1/authorized-storage-accounts/{account_id}' +GV_NODE_ADDON_ENDPOINT = 'http://192.168.168.167:8004/v1/configured-storage-addons/{addon_id}' +GV_EXTERNAL_STORAGE_ENDPOINT = 'http://192.168.168.167:8004/v1/external-storage-services/{service_id}' \ No newline at end of file