Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENG-5430] ENABLE_GV feature flipping for get_addon #10610

Draft
wants to merge 16 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions addons/base/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import requests
import markupsafe
from os.path import basename
from website.settings import MFR_SERVER_URL


from api.caching import settings as cache_settings
from api.caching.utils import legacy_addon_cache

from website import settings


Expand Down Expand Up @@ -54,3 +59,77 @@ def format_last_known_metadata(auth, node, file, error_type):
]
return ''.join(parts)
return msg


class GravyValetAddonAppConfig:

@staticmethod
def get_configured_storage_addons_data(config_id, user):
from osf.external.gravy_valet import auth_helpers as gv_auth
url = settings.GV_NODE_ADDON_ENDPOINT.format(config_id=config_id)

auth_headers = gv_auth.make_gravy_valet_hmac_headers(
request_url=url,
request_method='GET',
requesting_user=user,
)

resp = requests.get(url, headers=auth_headers)
resp.raise_for_status()
return resp.json()

@staticmethod
def get_authorized_storage_account(config_id, user):
from osf.external.gravy_valet import auth_helpers as gv_auth
url = settings.GV_USER_ADDON_ENDPOINT.format(config_id=config_id)
auth_headers = gv_auth.make_gravy_valet_hmac_headers(
request_url=url,
request_method='GET',
requesting_user=user,
)

resp = requests.get(url, headers=auth_headers)
resp.raise_for_status()
return resp.json()

def cache_config_id_translation(self):
"""
Cache what legacy addon name corresponds to which config ids.
"""

key = cache_settings.LEGACY_ADDON_KEY.format(target_id=self.config_id)
legacy_addon_cache.set(key, self.addon_name, settings.STORAGE_USAGE_CACHE_TIMEOUT)

def __init__(self, resource, config_id, user):
self.resource = resource
self.user = user
self.FOLDER_SELECTED = self.legacy_app_config.FOLDER_SELECTED
self.NODE_AUTHORIZED = self.legacy_app_config.NODE_DEAUTHORIZED
self.NODE_DEAUTHORIZED = self.legacy_app_config.NODE_DEAUTHORIZED
self.actions = self.legacy_app_config.actions

from osf.models import OSFUser, AbstractNode
if isinstance(resource, AbstractNode):
self.gv_data = self.get_configured_storage_addons_data(config_id, user)
elif isinstance(resource, OSFUser):
self.gv_data = self.get_authorized_storage_account(config_id, user)
else:
raise NotImplementedError()

# TODO: Names in GV must be exact matches?
self.addon_name = self.gv_data['data']['embeds']['external_storage_service']['attributes']['name']
self.cache_config_id_translation()
self.legacy_app_config = settings.ADDONS_AVAILABLE_DICT[self.addon_name]


@property
def config(self):
return self.legacy_app_config

@property
def configured(self):
return True

@property
def config_id(self):
return self.gv_data.config_id
9 changes: 9 additions & 0 deletions api/base/settings/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,9 @@

WAFFLE_CACHE_NAME = 'waffle_cache'
STORAGE_USAGE_CACHE_NAME = 'storage_usage'
LEGACY_ADDON_CACHE_NAME = 'legacy_addon'
STORAGE_USAGE_MAX_ENTRIES = 10000000
LEGACY_ADDON_MAX_ENTRIES = 999999999999


CACHES = {
Expand All @@ -359,6 +361,13 @@
'MAX_ENTRIES': STORAGE_USAGE_MAX_ENTRIES,
},
},
LEGACY_ADDON_CACHE_NAME: {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'osf_cache_table',
'OPTIONS': {
'MAX_ENTRIES': LEGACY_ADDON_MAX_ENTRIES,
},
},
WAFFLE_CACHE_NAME: {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
},
Expand Down
7 changes: 4 additions & 3 deletions api/base/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@ def get_user_auth(request):
"""Given a Django request object, return an ``Auth`` object with the
authenticated user attached to it.
"""
user = request.user
private_key = request.query_params.get('view_only', None)
if user.is_anonymous:
user = getattr(request, 'user', None)
query_params = getattr(request, 'query_params', {})
private_key = query_params.get('view_only', None)
if not user or user.is_anonymous:
auth = Auth(None, private_key=private_key)
else:
auth = Auth(user, private_key=private_key)
Expand Down
36 changes: 33 additions & 3 deletions api/base/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import waffle
from builtins import str

from collections import defaultdict
from distutils.version import StrictVersion
from framework.auth import Auth

from django_bulk_update.helper import bulk_update
from django.conf import settings as django_settings
Expand Down Expand Up @@ -40,11 +42,12 @@
from api.nodes.permissions import ExcludeWithdrawals
from api.users.serializers import UserSerializer
from framework.auth.oauth_scopes import CoreScopes
from osf import features
from osf.models import Contributor, MaintenanceState, BaseFileNode
from osf.utils.permissions import API_CONTRIBUTOR_PERMISSIONS, READ, WRITE, ADMIN
from waffle.models import Flag, Switch, Sample
from waffle import sample_is_active

from addons.base.utils import GravyValetAddonAppConfig

class JSONAPIBaseView(generics.GenericAPIView):

Expand Down Expand Up @@ -626,14 +629,28 @@ def bulk_get_file_nodes_from_wb_resp(self, files_list):
"""
node = self.get_node(check_object_permissions=False)
content_type = ContentType.objects.get_for_model(node)
from addons.base.utils import GravyValetAddonAppConfig

objs_to_create = defaultdict(lambda: [])
file_objs = []

provider_short_name = files_list[0]['provider']

if waffle.flag_is_active(self.request, features.ENABLE_GV):
user = getattr(self.request, 'user', None)
gv_config = GravyValetAddonAppConfig(
resource=self,
config_id=provider_short_name,
auth=user,
)
provider_short_name = gv_config.addon_name


for item in files_list:
attrs = item['attributes']

base_class = BaseFileNode.resolve_class(
attrs['provider'],
provider_short_name,
BaseFileNode.FOLDER if attrs['kind'] == 'folder'
else BaseFileNode.FILE,
)
Expand All @@ -660,6 +677,10 @@ def bulk_get_file_nodes_from_wb_resp(self, files_list):
else:
file_objs.append(file_obj)

# TODO: Improve robustness
if waffle.flag_is_active(self.request, features.ENABLE_GV):
file_obj.provider = gv_config.legacy_app_config.config_id

file_obj.update(None, attrs, user=self.request.user, save=False)

bulk_update(file_objs)
Expand All @@ -674,8 +695,17 @@ def bulk_get_file_nodes_from_wb_resp(self, files_list):
def get_file_node_from_wb_resp(self, item):
"""Takes file data from wb response, touches/updates metadata for it, and returns file object"""
attrs = item['attributes']
if waffle.flag_is_active(self.request, features.ENABLE_GV):
provider_determinent = GravyValetAddonAppConfig(
self,
attrs['provider'],
get_user_auth(self.request),
).legacy_app_config.short_name
else:
provider_determinent = attrs['provider']

file_node = BaseFileNode.resolve_class(
attrs['provider'],
provider_determinent,
BaseFileNode.FOLDER if attrs['kind'] == 'folder'
else BaseFileNode.FILE,
).get_or_create(self.get_node(check_object_permissions=False), attrs['path'])
Expand Down
1 change: 1 addition & 0 deletions api/caching/settings.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
NEVER_TIMEOUT = None # for django caches setting None as a timeout value means the cache never times out.

STORAGE_USAGE_KEY = 'storage_usage:{target_id}'
LEGACY_ADDON_KEY = 'legacy_addon:{target_id}'
1 change: 1 addition & 0 deletions api/caching/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
from django.conf import settings

storage_usage_cache = caches[settings.STORAGE_USAGE_CACHE_NAME]
legacy_addon_cache = caches[settings.LEGACY_ADDON_CACHE_NAME]
8 changes: 7 additions & 1 deletion api/nodes/serializers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import waffle
from django.db import connection
from distutils.version import StrictVersion

Expand Down Expand Up @@ -33,6 +34,7 @@
from rest_framework import exceptions
from addons.base.exceptions import InvalidAuthError, InvalidFolderError
from addons.osfstorage.models import Region
from osf import features
from osf.exceptions import NodeStateError
from osf.models import (
Comment, DraftRegistration, ExternalAccount, Institution,
Expand Down Expand Up @@ -1448,7 +1450,11 @@ class Meta:

@staticmethod
def get_id(obj):
return '{}:{}'.format(obj.node._id, obj.provider)
from osf.utils.requests import get_current_request
if waffle.flag_is_active(get_current_request(), features.ENABLE_GV):
return obj.provider
else:
return f'{obj.node._id}:{obj.provider}'

def get_absolute_url(self, obj):
return absolute_reverse(
Expand Down
9 changes: 6 additions & 3 deletions api/nodes/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,16 @@ def get_file_object(target, path, provider, request):
obj = get_object_or_error(model, Q(target_object_id=target.pk, target_content_type=content_type, _id=path.strip('/')), request)
return obj

if isinstance(target, AbstractNode) and not target.get_addon(provider) or not target.get_addon(provider).configured:
raise NotFound('The {} provider is not configured for this project.'.format(provider))
addon = target.get_addon(provider)

if isinstance(target, AbstractNode) and not addon or not addon.configured:
raise NotFound(f'The {provider} provider is not configured for this project.')

view_only = request.query_params.get('view_only', default=None)
base_url = None
if hasattr(target, 'osfstorage_region'):
base_url = target.osfstorage_region.waterbutler_url

url = waterbutler_api_url_for(
target._id, provider, path, _internal=True,
base_url=base_url, meta=True, view_only=view_only,
Expand All @@ -56,7 +59,7 @@ def get_file_object(target, path, provider, request):
headers={'Authorization': request.META.get('HTTP_AUTHORIZATION')},
)

if waterbutler_request.status_code == 401:
if waterbutler_request.status_code in (401, 403):
raise PermissionDenied

if waterbutler_request.status_code == 404:
Expand Down
Loading
Loading