def get_default_queryset(self): provider = self.get_provider() group_helper = GroupHelper(provider) admin_group = group_helper.get_group('admin') mod_group = group_helper.get_group('moderator') return (admin_group.user_set.all() | mod_group.user_set.all()).annotate(permission_group=Case( When(groups=admin_group, then=Value('admin')), default=Value('moderator'), output_field=CharField() )).order_by('fullname')
def update_group_auth(apps, schema): emit_post_migrate_signal(2, False, 'default') PreprintProvider = apps.get_model('osf', 'preprintprovider') [ GroupHelper(each).update_provider_auth_groups() for each in PreprintProvider.objects.all() ]
def populate_provider_notification_subscriptions(apps, schema_editor): NotificationSubscription = apps.get_model('osf', 'NotificationSubscription') PreprintProvider = apps.get_model('osf', 'PreprintProvider') for provider in PreprintProvider.objects.all(): helper = GroupHelper(provider) try: provider_admins = helper.get_group('admin').user_set.all() provider_moderators = helper.get_group('moderator').user_set.all() except Group.DoesNotExist: logger.warn('Unable to find groups for provider "{}", assuming there are no subscriptions to create.'.format(provider._id)) continue instance, created = NotificationSubscription.objects.get_or_create(_id='{provider_id}_new_pending_submissions'.format(provider_id=provider._id), event_name='new_pending_submissions', provider=provider) for user in provider_admins | provider_moderators: # add user to subscription list but set their notification to none by default instance.add_user_to_subscription(user, 'email_transactional', save=True)
def test_create_permissions(self, mock_ezid, app, url, preprint, node_admin, moderator): assert preprint.machine_state == 'initial' submit_payload = self.create_payload(preprint._id, trigger='submit') # Unauthorized user can't submit res = app.post_json_api(url, submit_payload, expect_errors=True) assert res.status_code == 401 # A random user can't submit some_rando = AuthUserFactory() res = app.post_json_api(url, submit_payload, auth=some_rando.auth, expect_errors=True) assert res.status_code == 403 # Node admin can submit res = app.post_json_api(url, submit_payload, auth=node_admin.auth) assert res.status_code == 201 preprint.refresh_from_db() assert preprint.machine_state == 'pending' assert not preprint.is_published accept_payload = self.create_payload(preprint._id, trigger='accept', comment='This is good.') # Unauthorized user can't accept res = app.post_json_api(url, accept_payload, expect_errors=True) assert res.status_code == 401 # A random user can't accept res = app.post_json_api(url, accept_payload, auth=some_rando.auth, expect_errors=True) assert res.status_code == 403 # Moderator from another provider can't accept another_moderator = AuthUserFactory() another_moderator.groups.add(GroupHelper(PreprintProviderFactory()).get_group('moderator')) res = app.post_json_api(url, accept_payload, auth=another_moderator.auth, expect_errors=True) assert res.status_code == 403 # Node admin can't accept res = app.post_json_api(url, accept_payload, auth=node_admin.auth, expect_errors=True) assert res.status_code == 403 # Still unchanged after all those tries preprint.refresh_from_db() assert preprint.machine_state == 'pending' assert not preprint.is_published # Moderator can accept res = app.post_json_api(url, accept_payload, auth=moderator.auth) assert res.status_code == 201 preprint.refresh_from_db() assert preprint.machine_state == 'accepted' assert preprint.is_published # Check if "get_and_set_preprint_identifiers" is called once. assert mock_ezid.call_count == 1
def add_to_group(self, user, group): from api.preprint_providers.permissions import GroupHelper # Add default notification subscription notification = self.notification_subscriptions.get(_id='{}_new_pending_submissions'.format(self._id)) user_id = user.id is_subscriber = notification.none.filter(id=user_id).exists() \ or notification.email_digest.filter(id=user_id).exists() \ or notification.email_transactional.filter(id=user_id).exists() if not is_subscriber: notification.add_user_to_subscription(user, 'email_transactional', save=True) return GroupHelper(self).get_group(group).user_set.add(user)
def remove_from_group(self, user, group, unsubscribe=True): from api.preprint_providers.permissions import GroupHelper _group = GroupHelper(self).get_group(group) if group == 'admin': if _group.user_set.filter(id=user.id).exists() and not _group.user_set.exclude(id=user.id).exists(): raise ValueError('Cannot remove last admin.') if unsubscribe: # remove notification subscription notification = self.notification_subscriptions.get(_id='{}_new_pending_submissions'.format(self._id)) notification.remove_user_from_subscription(user, save=True) return _group.user_set.remove(user)
def test_list_moderators_alphabetically(self, app, url, admin, moderator, provider): admin.fullname = 'Alice Alisdottir' moderator.fullname = 'Bob Bobsson' new_mod = AuthUserFactory(fullname='Cheryl Cherylsdottir') GroupHelper(provider).get_group('moderator').user_set.add(new_mod) admin.save() moderator.save() res = app.get(url, auth=admin.auth) assert len(res.json['data']) == 3 assert res.json['data'][0]['id'] == admin._id assert res.json['data'][1]['id'] == moderator._id assert res.json['data'][2]['id'] == new_mod._id
def user(self, request, preprint): user = AuthUserFactory() if request.param: user.groups.add( GroupHelper(preprint.provider).get_group('moderator')) else: preprint.node.add_contributor(user, permissions=[ osf_permissions.READ, osf_permissions.WRITE, osf_permissions.ADMIN ]) return user
def handle(self, *args, **options): dry = options.get('dry_run') # Start a transaction that will be rolled back if any exceptions are raised with transaction.atomic(): for cls in ReviewProviderMixin.__subclasses__(): for provider in cls.objects.all(): logger.info('Updating auth groups for review provider %s', provider) GroupHelper(provider).update_provider_auth_groups() if dry: # When running in dry mode force the transaction to rollback raise Exception('Abort Transaction - Dry Run')
def test_permissions(self, app, url, preprint_one, preprint_two, preprint_three, group_name): another_user = AuthUserFactory() preprints = (preprint_one, preprint_two, preprint_three) for preprint in preprints: preprint.is_published = False preprint.save() def actual(): res = app.get(url, auth=another_user.auth) return set([preprint['id'] for preprint in res.json['data']]) expected = set() assert expected == actual() for preprint in preprints: another_user.groups.add( GroupHelper(preprint.provider).get_group(group_name)) expected.update([ p._id for p in preprints if p.provider_id == preprint.provider_id ]) assert expected == actual()
def create_provider_auth_groups(apps, schema_editor): # this is to make sure that the permissions created in an earlier migration exist! emit_post_migrate_signal(2, False, 'default') PreprintProvider = apps.get_model('osf', 'PreprintProvider') for provider in PreprintProvider.objects.all(): GroupHelper(provider).update_provider_auth_groups()
def moderator(self, provider): moderator = AuthUserFactory() moderator.groups.add(GroupHelper(provider).get_group('moderator')) return moderator
def moderator(self, preprint_provider): user = AuthUserFactory() user.groups.add(GroupHelper(preprint_provider).get_group('moderator')) return user
def moderator(self, provider): user = AuthUserFactory() GroupHelper(provider).get_group('moderator').user_set.add(user) return user
def user(self, allowed_providers): user = AuthUserFactory() for provider in allowed_providers: user.groups.add(GroupHelper(provider).get_group('moderator')) return user
def admin_pair(self, expected_providers): user = AuthUserFactory() provider = expected_providers[1] user.groups.add(GroupHelper(provider).get_group('admin')) return (user, provider)
def moderator_pair(self, expected_providers): user = AuthUserFactory() provider = expected_providers[0] user.groups.add(GroupHelper(provider).get_group('moderator')) return (user, provider)
def create_provider_auth_groups(sender, instance, created, **kwargs): if created: GroupHelper(instance).update_provider_auth_groups()
def provider(self): pp = PreprintProviderFactory(name='ModArxiv') GroupHelper(pp).update_provider_auth_groups() return pp
def resolve_guid(guid, suffix=None): """Load GUID by primary key, look up the corresponding view function in the routing table, and return the return value of the view function without changing the URL. :param str guid: GUID primary key :param str suffix: Remainder of URL after the GUID :return: Return value of proxied view function """ try: # Look up guid_object = Guid.load(guid) except KeyError as e: if e.message == 'osfstorageguidfile': # Used when an old detached OsfStorageGuidFile object is accessed raise HTTPError(http.NOT_FOUND) else: raise e if guid_object: # verify that the object implements a GuidStoredObject-like interface. If a model # was once GuidStoredObject-like but that relationship has changed, it's # possible to have referents that are instances of classes that don't # have a deep_url attribute or otherwise don't behave as # expected. if not hasattr(guid_object.referent, 'deep_url'): sentry.log_message('Guid resolved to an object with no deep_url', dict(guid=guid)) raise HTTPError(http.NOT_FOUND) referent = guid_object.referent if referent is None: logger.error('Referent of GUID {0} not found'.format(guid)) raise HTTPError(http.NOT_FOUND) if not referent.deep_url: raise HTTPError(http.NOT_FOUND) # Handle file `/download` shortcut with supported types. if suffix and suffix.rstrip('/').lower() == 'download': file_referent = None if isinstance(referent, PreprintService) and referent.primary_file: if not referent.is_published: # TODO: Ideally, permissions wouldn't be checked here. # This is necessary to prevent a logical inconsistency with # the routing scheme - if a preprint is not published, only # admins and moderators should be able to know it exists. auth = Auth.from_kwargs(request.args.to_dict(), {}) group_helper = GroupHelper(referent.provider) admin_group = group_helper.get_group('admin') mod_group = group_helper.get_group('moderator') # Check if user isn't a nonetype or that the user has admin/moderator permissions if auth.user is None or not ( referent.node.has_permission( auth.user, permissions.ADMIN) or (mod_group.user_set.all() | admin_group.user_set.all() ).filter(id=auth.user.id).exists()): raise HTTPError(http.NOT_FOUND) file_referent = referent.primary_file elif isinstance(referent, BaseFileNode) and referent.is_file: file_referent = referent if file_referent: # Extend `request.args` adding `action=download`. request.args = request.args.copy() request.args.update({'action': 'download'}) # Do not include the `download` suffix in the url rebuild. url = _build_guid_url(urllib.unquote(file_referent.deep_url)) return proxy_url(url) # Handle Ember Applications if isinstance(referent, PreprintService): if referent.provider.domain_redirect_enabled: # This route should always be intercepted by nginx for the branded domain, # w/ the exception of `<guid>/download` handled above. return redirect(referent.absolute_url, http.MOVED_PERMANENTLY) if PROXY_EMBER_APPS: resp = requests.get(EXTERNAL_EMBER_APPS['preprints']['server'], stream=True, timeout=EXTERNAL_EMBER_SERVER_TIMEOUT) return Response(stream_with_context(resp.iter_content()), resp.status_code) return send_from_directory(preprints_dir, 'index.html') if isinstance(referent, BaseFileNode ) and referent.is_file and referent.node.is_quickfiles: if referent.is_deleted: raise HTTPError(http.GONE) if PROXY_EMBER_APPS: resp = requests.get( EXTERNAL_EMBER_APPS['ember_osf_web']['server'], stream=True, timeout=EXTERNAL_EMBER_SERVER_TIMEOUT) return Response(stream_with_context(resp.iter_content()), resp.status_code) return send_from_directory(ember_osf_web_dir, 'index.html') if isinstance(referent, Node) and not referent.is_registration and suffix: page = suffix.strip('/').split('/')[0] if waffle.flag_is_active(request, 'ember_project_{}_page'.format(page)): use_ember_app() url = _build_guid_url(urllib.unquote(referent.deep_url), suffix) return proxy_url(url) # GUID not found; try lower-cased and redirect if exists guid_object_lower = Guid.load(guid.lower()) if guid_object_lower: return redirect(_build_guid_url(guid.lower(), suffix)) # GUID not found raise HTTPError(http.NOT_FOUND)
def admin(self, provider): user = AuthUserFactory() GroupHelper(provider).get_group('admin').user_set.add(user) return user
def admin(self, preprint_provider): user = AuthUserFactory() user.groups.add(GroupHelper(preprint_provider).get_group('admin')) return user
def add_moderator(self, user): from api.preprint_providers.permissions import GroupHelper return GroupHelper(self).get_group('moderator').user_set.add(user)
def resolve_guid(guid, suffix=None): """Load GUID by primary key, look up the corresponding view function in the routing table, and return the return value of the view function without changing the URL. :param str guid: GUID primary key :param str suffix: Remainder of URL after the GUID :return: Return value of proxied view function """ try: # Look up guid_object = Guid.load(guid) except KeyError as e: if e.message == 'osfstorageguidfile': # Used when an old detached OsfStorageGuidFile object is accessed raise HTTPError(http.NOT_FOUND) else: raise e if guid_object: # verify that the object implements a GuidStoredObject-like interface. If a model # was once GuidStoredObject-like but that relationship has changed, it's # possible to have referents that are instances of classes that don't # have a deep_url attribute or otherwise don't behave as # expected. if not hasattr(guid_object.referent, 'deep_url'): sentry.log_message( 'Guid resolved to an object with no deep_url', dict(guid=guid) ) raise HTTPError(http.NOT_FOUND) referent = guid_object.referent if referent is None: logger.error('Referent of GUID {0} not found'.format(guid)) raise HTTPError(http.NOT_FOUND) if not referent.deep_url: raise HTTPError(http.NOT_FOUND) # Handle file `/download` shortcut with supported types. if suffix and suffix.rstrip('/').lower() == 'download': file_referent = None if isinstance(referent, PreprintService) and referent.primary_file: if not referent.is_published: # TODO: Ideally, permissions wouldn't be checked here. # This is necessary to prevent a logical inconsistency with # the routing scheme - if a preprint is not published, only # admins and moderators should be able to know it exists. auth = Auth.from_kwargs(request.args.to_dict(), {}) group_helper = GroupHelper(referent.provider) admin_group = group_helper.get_group('admin') mod_group = group_helper.get_group('moderator') # Check if user isn't a nonetype or that the user has admin/moderator permissions if auth.user is None or not (referent.node.has_permission(auth.user, permissions.ADMIN) or (mod_group.user_set.all() | admin_group.user_set.all()).filter(id=auth.user.id).exists()): raise HTTPError(http.NOT_FOUND) file_referent = referent.primary_file elif isinstance(referent, BaseFileNode) and referent.is_file: file_referent = referent if file_referent: # Extend `request.args` adding `action=download`. request.args = request.args.copy() request.args.update({'action': 'download'}) # Do not include the `download` suffix in the url rebuild. url = _build_guid_url(urllib.unquote(file_referent.deep_url)) return proxy_url(url) # Handle Ember Applications if isinstance(referent, PreprintService): if referent.provider.domain_redirect_enabled: # This route should always be intercepted by nginx for the branded domain, # w/ the exception of `<guid>/download` handled above. return redirect(referent.absolute_url, http.MOVED_PERMANENTLY) if PROXY_EMBER_APPS: resp = requests.get(EXTERNAL_EMBER_APPS['preprints']['server'], stream=True, timeout=EXTERNAL_EMBER_SERVER_TIMEOUT) return Response(stream_with_context(resp.iter_content()), resp.status_code) return send_from_directory(preprints_dir, 'index.html') if isinstance(referent, BaseFileNode) and referent.is_file and referent.node.is_quickfiles: if referent.is_deleted: raise HTTPError(http.GONE) if PROXY_EMBER_APPS: resp = requests.get(EXTERNAL_EMBER_APPS['ember_osf_web']['server'], stream=True, timeout=EXTERNAL_EMBER_SERVER_TIMEOUT) return Response(stream_with_context(resp.iter_content()), resp.status_code) return send_from_directory(ember_osf_web_dir, 'index.html') url = _build_guid_url(urllib.unquote(referent.deep_url), suffix) return proxy_url(url) # GUID not found; try lower-cased and redirect if exists guid_object_lower = Guid.load(guid.lower()) if guid_object_lower: return redirect( _build_guid_url(guid.lower(), suffix) ) # GUID not found raise HTTPError(http.NOT_FOUND)