def serialize_comment(comment): reports = [ serialize_report(user, report) for user, report in comment.reports.iteritems() ] author_abs_url = furl(OSF_DOMAIN) author_abs_url.path.add(comment.user.url) return { 'id': comment._id, 'author': OSFUser.load(comment.user._id), 'author_id': comment.user._id, 'author_path': author_abs_url.url, 'date_created': comment.created, 'date_modified': comment.modified, 'content': comment.content, 'has_children': bool(getattr(comment, 'commented', [])), 'modified': comment.edited, 'is_deleted': comment.is_deleted, 'spam_status': comment.spam_status, 'reports': reports, 'node': comment.node, 'category': reports[0]['category'], 'osf_support_email': OSF_SUPPORT_EMAIL, }
def send_users_email(send_type): """Find pending Emails and amalgamates them into a single Email. :param send_type :return: """ grouped_emails = get_users_emails(send_type) for group in grouped_emails: user = OSFUser.load(group['user_id']) if not user: log_exception() continue info = group['info'] notification_ids = [message['_id'] for message in info] sorted_messages = group_by_node(info) if sorted_messages: if not user.is_disabled: # If there's only one node in digest we can show it's preferences link in the template. notification_nodes = sorted_messages['children'].keys() node = AbstractNode.load(notification_nodes[0]) if len( notification_nodes) == 1 else None mails.send_mail( to_addr=user.username, mimetype='html', can_change_node_preferences=bool(node), node=node, mail=mails.DIGEST, name=user.fullname, message=sorted_messages, ) remove_notifications(email_notification_ids=notification_ids)
def get_user_from_cas_resp(cas_resp): """ Given a CAS service validation response, attempt to retrieve user information and next action. The `user` in `cas_resp` is the unique GUID of the user. Please do not use the primary key `id` or the email `username`. This holds except for the first step of ORCiD login. :param cas_resp: the cas service validation response :return: the user, the external_credential, and the next action """ from osf.models import OSFUser if cas_resp.user: user = OSFUser.load(cas_resp.user) # cas returns a valid OSF user id if user: return user, None, 'authenticate' # cas does not return a valid OSF user id else: external_credential = validate_external_credential(cas_resp.user) # invalid cas response if not external_credential: return None, None, None # cas returns a valid external credential user = get_user( external_id_provider=external_credential['provider'], external_id=external_credential['id']) # existing user found if user: return user, external_credential, 'authenticate' # user first time login through external identity provider else: return None, external_credential, 'external_first_login'
def form_valid(self, form): user_id = form.cleaned_data.get('user_id') osf_user = OSFUser.load(user_id) if not osf_user: raise Http404( 'OSF user with id "{}" not found. Please double check.'.format( user_id)) for group in form.cleaned_data.get('group_perms'): osf_user.groups.add(group) split = group.name.split('_') group_type = split[0] if group_type == 'reviews': provider_id = split[2] provider = PreprintProvider.objects.get(id=provider_id) provider.notification_subscriptions.get( event_name='new_pending_submissions' ).add_user_to_subscription(osf_user, 'email_transactional') osf_user.save() messages.success( self.request, 'Permissions update successful for OSF User {}!'.format( osf_user.username)) return super(PreprintProviderRegisterModeratorOrAdmin, self).form_valid(form)
def reset_password_get(auth, uid=None, token=None): """ View for user to land on the reset password page. HTTp Method: GET :param auth: the authentication state :param uid: the user id :param token: the token in verification key :return :raises: HTTPError(http.BAD_REQUEST) if verification key for the user is invalid, has expired or was used """ # if users are logged in, log them out and redirect back to this page if auth.logged_in: return auth_logout(redirect_url=request.url) # Check if request bears a valid pair of `uid` and `token` user_obj = OSFUser.load(uid) if not (user_obj and user_obj.verify_password_token(token=token)): error_data = { 'message_short': 'Invalid Request.', 'message_long': 'The requested URL is invalid, has expired, or was already used', } raise HTTPError(http.BAD_REQUEST, data=error_data) # refresh the verification key (v2) user_obj.verification_key_v2 = generate_verification_key(verification_type='password') user_obj.save() return { 'uid': user_obj._id, 'token': user_obj.verification_key_v2['token'], }
def _get_current_user(): from osf.models import OSFUser current_user_id = get_current_user_id() if current_user_id: return OSFUser.load(current_user_id, select_for_update=check_select_for_update(request)) else: return None
def osfstorage_delete(file_node, payload, target, **kwargs): user = OSFUser.load(payload['user']) auth = Auth(user) #TODO Auth check? if not auth: raise HTTPError(httplib.BAD_REQUEST) if file_node == OsfStorageFolder.objects.get_root(target=target): raise HTTPError(httplib.BAD_REQUEST) try: file_node.delete(user=user) except exceptions.FileNodeCheckedOutError: raise HTTPError(httplib.FORBIDDEN) except exceptions.FileNodeIsPrimaryFile: raise HTTPError( httplib.FORBIDDEN, data={ 'message_long': 'Cannot delete file as it is the primary file of preprint.' }) update_storage_usage(file_node.target) return {'status': 'success'}
def test_make_child_embargoed_registration_public_asks_all_admins_in_tree( self, mock_ask): # Initiate and approve embargo node = NodeFactory(creator=self.user) c1 = AuthUserFactory() child = NodeFactory(parent=node, creator=c1) c2 = AuthUserFactory() NodeFactory(parent=child, creator=c2) registration = RegistrationFactory(project=node) registration.embargo_registration( self.user, timezone.now() + datetime.timedelta(days=10)) for user_id, embargo_tokens in registration.embargo.approval_state.iteritems( ): approval_token = embargo_tokens['approval_token'] registration.embargo.approve_embargo(OSFUser.load(user_id), approval_token) self.registration.save() registration.set_privacy('public', Auth(self.registration.creator)) asked_admins = [(admin._id, n._id) for admin, n in mock_ask.call_args[0][0]] for admin, node in registration.get_admin_contributors_recursive(): assert_in((admin._id, node._id), asked_admins)
def post(self, request, *args, **kwargs): registration_provider = RegistrationProvider.objects.get( id=self.kwargs['registration_provider_id']) data = dict(request.POST) del data[ 'csrfmiddlewaretoken'] # just to remove the key from the form dict moderator = OSFUser.load(data['add-moderators-form'][0]) if moderator is None: messages.error( request, f'User for guid: {data["add-moderators-form"][0]} could not be found' ) return redirect('registration_providers:add_moderators', registration_provider_id=registration_provider.id) registration_provider.add_to_group(moderator, 'moderator') messages.success( request, f'The following moderator was successfully added: {moderator.fullname} ({moderator.username})' ) return redirect('registration_providers:add_moderators', registration_provider_id=registration_provider.id)
def test_embargoed_registration_set_privacy_sends_mail( self, mock_send_mail): """ Integration test for https://github.com/CenterForOpenScience/osf.io/pull/5294#issuecomment-212613668 """ # Initiate and approve embargo for i in range(3): c = AuthUserFactory() self.registration.add_contributor(c, [permissions.ADMIN], auth=Auth(self.user)) self.registration.save() self.registration.embargo_registration( self.user, timezone.now() + datetime.timedelta(days=10)) for user_id, embargo_tokens in self.registration.embargo.approval_state.iteritems( ): approval_token = embargo_tokens['approval_token'] self.registration.embargo.approve_embargo(OSFUser.load(user_id), approval_token) self.registration.save() self.registration.set_privacy('public', Auth(self.registration.creator)) for admin in self.registration.admin_contributors: assert_true( any([ each[0][0] == admin.username for each in mock_send_mail.call_args_list ]))
def test_embargoed_registration_set_privacy_requests_embargo_termination( self, mock_ask): # Initiate and approve embargo for i in range(3): c = AuthUserFactory() self.registration.add_contributor(c, [permissions.ADMIN], auth=Auth(self.user)) self.registration.save() self.registration.embargo_registration( self.user, timezone.now() + datetime.timedelta(days=10)) for user_id, embargo_tokens in self.registration.embargo.approval_state.iteritems( ): approval_token = embargo_tokens['approval_token'] self.registration.embargo.approve_embargo(OSFUser.load(user_id), approval_token) self.registration.save() self.registration.set_privacy('public', Auth(self.registration.creator)) for reg in self.registration.node_and_primary_descendants(): reg.reload() assert_false(reg.is_public) assert_true(reg.embargo_termination_approval) assert_true(reg.embargo_termination_approval.is_pending_approval)
def authenticate(self, request): """ Check whether the request provides a valid OAuth2 bearer token. The `user` in `cas_auth_response` is the unique GUID of the user. Please do not use the primary key `id` or the email `username`. :param request: the request :return: the user who owns the bear token and the cas repsonse """ client = cas.get_client() try: auth_header_field = request.META['HTTP_AUTHORIZATION'] auth_token = cas.parse_auth_header(auth_header_field) except (cas.CasTokenError, KeyError): return None try: cas_auth_response = client.profile(auth_token) except cas.CasHTTPError: raise exceptions.NotAuthenticated(_('User provided an invalid OAuth2 access token')) if cas_auth_response.authenticated is False: raise exceptions.NotAuthenticated(_('CAS server failed to authenticate this token')) user = OSFUser.load(cas_auth_response.user) if not user: raise exceptions.AuthenticationFailed(_('Could not find the user associated with this token')) check_user(user) return user, cas_auth_response
def get_user_from_cas_resp(cas_resp): """ Given a CAS service validation response, attempt to retrieve user information and next action. The `user` in `cas_resp` is the unique GUID of the user. Please do not use the primary key `id` or the email `username`. This holds except for the first step of ORCiD login. :param cas_resp: the cas service validation response :return: the user, the external_credential, and the next action """ from osf.models import OSFUser if cas_resp.user: user = OSFUser.load(cas_resp.user) # cas returns a valid OSF user id if user: return user, None, 'authenticate' # cas does not return a valid OSF user id else: external_credential = validate_external_credential(cas_resp.user) # invalid cas response if not external_credential: return None, None, None # cas returns a valid external credential user = get_user(external_id_provider=external_credential['provider'], external_id=external_credential['id']) # existing user found if user: return user, external_credential, 'authenticate' # user first time login through external identity provider else: return None, external_credential, 'external_first_login'
def send_users_email(send_type): """Find pending Emails and amalgamates them into a single Email. :param send_type :return: """ grouped_emails = get_users_emails(send_type) if not grouped_emails: return for group in grouped_emails: user = User.load(group['user_id']) if not user: log_exception() continue info = group['info'] notification_ids = [message['_id'] for message in info] sorted_messages = group_by_node(info) if sorted_messages: mails.send_mail(to_addr=user.username, mimetype='html', mail=mails.DIGEST, name=user.fullname, message=sorted_messages, callback=remove_notifications( email_notification_ids=notification_ids))
def send_users_email(send_type): """Find pending Emails and amalgamates them into a single Email. :param send_type :return: """ grouped_emails = get_users_emails(send_type) for group in grouped_emails: user = OSFUser.load(group['user_id']) if not user: log_exception() continue info = group['info'] notification_ids = [message['_id'] for message in info] sorted_messages = group_by_node(info) if sorted_messages: if not user.is_disabled: mails.send_mail( to_addr=user.username, mimetype='html', mail=mails.DIGEST, name=user.fullname, message=sorted_messages, ) remove_notifications(email_notification_ids=notification_ids)
def process_project_search_results(results, **kwargs): """ :param results: list of projects from the modular ODM search :return: we return the entire search result, which is a list of dictionaries. This includes the list of contributors. """ user = kwargs['auth'].user ret = [] for project in results: authors = get_node_contributors_abbrev(project=project, auth=kwargs['auth']) authors_html = '' for author in authors['contributors']: a = OSFUser.load(author['user_id']) authors_html += '<a href="%s">%s</a>' % (a.url, a.fullname) authors_html += author['separator'] + ' ' authors_html += ' ' + authors['others_count'] ret.append({ 'id': project._id, 'label': project.title, 'value': project.title, 'category': 'My Projects' if user in project.contributors else 'Public Projects', 'authors': authors_html, }) return ret
def get_public_components(uid=None, user=None): user = user or User.load(uid) rel_child_ids = ( NodeRelation.objects.filter( child__is_public=True, child__type='osf.node', # nodes only (not collections or registration) child___contributors=user, # user is a contributor is_node_link=False # exclude childs by node linkage ) .exclude(parent__type='osf.collection') .exclude(child__is_deleted=True) .values_list('child_id', flat=True) ) nodes = (Node.objects.filter(id__in=rel_child_ids) .include('contributor__user__guids', 'guids', '_parents__parent__guids') # Defer some fields that we don't use for rendering node lists .defer('child_node_subscriptions', 'date_created', 'deleted_date', 'description', 'file_guid_to_share_uuids') .order_by('-date_modified')) return [ serialize_node_summary(node=node, auth=Auth(user), show_path=True) for node in nodes ]
def deserialize_contributors(node, user_dicts, auth, validate=False): """View helper that returns a list of User objects from a list of serialized users (dicts). The users in the list may be registered or unregistered users. e.g. ``[{'id': 'abc123', 'registered': True, 'fullname': ..}, {'id': None, 'registered': False, 'fullname'...}, {'id': '123ab', 'registered': False, 'fullname': ...}] If a dict represents an unregistered user without an ID, creates a new unregistered User record. :param Node node: The node to add contributors to :param list(dict) user_dicts: List of serialized users in the format above. :param Auth auth: :param bool validate: Whether to validate and sanitize fields (if necessary) """ # Add the registered contributors contribs = [] for contrib_dict in user_dicts: fullname = contrib_dict['fullname'] visible = contrib_dict['visible'] email = contrib_dict.get('email') if validate is True: # Validate and sanitize inputs as needed. Email will raise error if invalid. # TODO Edge case bug: validation and saving are performed in same loop, so all in list # up to the invalid entry will be saved. (communicate to the user what needs to be retried) fullname = sanitize.strip_html(fullname) if not fullname: raise ValidationError('Full name field cannot be empty') if email: validate_email(email) # Will raise a ValidationError if email invalid if contrib_dict['id']: contributor = OSFUser.load(contrib_dict['id']) else: try: contributor = OSFUser.create_unregistered( fullname=fullname, email=email) contributor.save() except ValidationError: ## FIXME: This suppresses an exception if ID not found & new validation fails; get_user will return None contributor = get_user(email=email) # Add unclaimed record if necessary if not contributor.is_registered: contributor.add_unclaimed_record(node, referrer=auth.user, given_name=fullname, email=email) contributor.save() contribs.append({ 'user': contributor, 'visible': visible, 'permissions': expand_permissions(contrib_dict.get('permission')) }) return contribs
def _get_current_user(): from osf.models import OSFUser current_user_id = get_current_user_id() if current_user_id: return OSFUser.load(current_user_id) else: return None
def _send_global_and_node_emails(send_type): """ Called by `send_users_email`. Send all global and node-related notification emails. """ grouped_emails = get_users_emails(send_type) for group in grouped_emails: user = OSFUser.load(group['user_id']) if not user: log_exception() continue info = group['info'] notification_ids = [message['_id'] for message in info] sorted_messages = group_by_node(info) if sorted_messages: if not user.is_disabled: # If there's only one node in digest we can show it's preferences link in the template. notification_nodes = sorted_messages['children'].keys() node = AbstractNode.load(notification_nodes[0]) if len( notification_nodes) == 1 else None mails.send_mail( to_addr=user.username, mimetype='html', can_change_node_preferences=bool(node), node=node, mail=mails.DIGEST, name=user.fullname, message=sorted_messages, ) remove_notifications(email_notification_ids=notification_ids)
def get_contributors(self, obj): contributor_info = [] if is_anonymized(self.context['request']): return contributor_info contributor_ids = obj.get('contributors', None) params_node = obj.get('node', None) if contributor_ids: for contrib_id in contributor_ids: user = OSFUser.load(contrib_id) unregistered_name = None if user.unclaimed_records.get(params_node): unregistered_name = user.unclaimed_records[params_node].get('name', None) contributor_info.append({ 'id': contrib_id, 'full_name': user.fullname, 'given_name': user.given_name, 'middle_names': user.middle_names, 'family_name': user.family_name, 'unregistered_name': unregistered_name, 'active': user.is_active }) return contributor_info
def save(self, *args, **kwargs): first_save = not bool(self.pk) saved_fields = self.get_dirty_fields() or [] old_subjects = kwargs.pop('old_subjects', []) if saved_fields: request, user_id = get_request_and_user_id() request_headers = {} if not isinstance(request, DummyRequest): request_headers = { k: v for k, v in get_headers_from_request(request).items() if isinstance(v, basestring) } user = OSFUser.load(user_id) if user: self.check_spam(user, saved_fields, request_headers) if not first_save and ('ever_public' in saved_fields and saved_fields['ever_public']): raise ValidationError('Cannot set "ever_public" to False') ret = super(Preprint, self).save(*args, **kwargs) if first_save: self._set_default_region() self.update_group_permissions() self._add_creator_as_contributor() if (not first_save and 'is_published' in saved_fields) or self.is_published: update_or_enqueue_on_preprint_updated(preprint_id=self._id, old_subjects=old_subjects, saved_fields=saved_fields) return ret
def check_spam(self): request, user_id = get_request_and_user_id() user = OSFUser.load(user_id) if not isinstance(request, DummyRequest): request_headers = { k: v for k, v in get_headers_from_request(request).items() if isinstance(v, basestring) } node = self.wiki_page.node if not settings.SPAM_CHECK_ENABLED: return False if settings.SPAM_CHECK_PUBLIC_ONLY and not node.is_public: return False if 'ham_confirmed' in user.system_tags: return False content = self._get_spam_content(node) if not content: return is_spam = node.do_check_spam( user.fullname, user.username, content, request_headers ) logger.info("Node ({}) '{}' smells like {} (tip: {})".format( node._id, node.title.encode('utf-8'), 'SPAM' if is_spam else 'HAM', node.spam_pro_tip )) if is_spam: node._check_spam_user(user) return is_spam
def subscribe_mailchimp(list_name, user_id): user = User.load(user_id) m = get_mailchimp_api() list_id = get_list_id_from_name(list_name=list_name) if user.mailchimp_mailing_lists is None: user.mailchimp_mailing_lists = {} try: m.lists.subscribe( id=list_id, email={'email': user.username}, merge_vars={ 'fname': user.given_name, 'lname': user.family_name, }, double_optin=False, update_existing=True, ) except (mailchimp.ValidationError, mailchimp.ListInvalidBounceMemberError) as error: sentry.log_exception() sentry.log_message(error.message) user.mailchimp_mailing_lists[list_name] = False else: user.mailchimp_mailing_lists[list_name] = True finally: user.save()
def osfstorage_create_child(file_node, payload, node_addon, **kwargs): parent = file_node # Just for clarity name = payload.get('name') user = OSFUser.load(payload.get('user')) is_folder = payload.get('kind') == 'folder' if not (name or user) or '/' in name: raise HTTPError(httplib.BAD_REQUEST) try: # Create a save point so that we can rollback and unlock # the parent record with transaction.atomic(): if is_folder: created, file_node = True, parent.append_folder(name) else: created, file_node = True, parent.append_file(name) except (ValidationError, IntegrityError): created, file_node = False, parent.find_child_by_name(name, kind=int(not is_folder)) if not created and is_folder: raise HTTPError(httplib.CONFLICT, data={ 'message': 'Cannot create folder "{name}" because a file or folder already exists at path "{path}"'.format( name=file_node.name, path=file_node.materialized_path, ) }) if not is_folder: try: if file_node.checkout is None or file_node.checkout._id == user._id: version = file_node.create_version( user, dict(payload['settings'], **dict( payload['worker'], **{ 'object': payload['metadata']['name'], 'service': payload['metadata']['provider'], }) ), dict(payload['metadata'], **payload['hashes']) ) version_id = version._id archive_exists = version.archive is not None else: raise HTTPError(httplib.FORBIDDEN, data={ 'message_long': 'File cannot be updated due to checkout status.' }) except KeyError: raise HTTPError(httplib.BAD_REQUEST) else: version_id = None archive_exists = False return { 'status': 'success', 'archive': not archive_exists, # Should waterbutler also archive this file 'data': file_node.serialize(), 'version': version_id, }, httplib.CREATED if created else httplib.OK
def send_claim_registered_email(claimer, unclaimed_user, node, throttle=24 * 3600): """ A registered user claiming the unclaimed user account as an contributor to a project. Send an email for claiming the account to the referrer and notify the claimer. :param claimer: the claimer :param unclaimed_user: the user account to claim :param node: the project node where the user account is claimed :param throttle: the time period in seconds before another claim for the account can be made :return: :raise: http.BAD_REQUEST """ unclaimed_record = unclaimed_user.get_unclaimed_record(node._primary_key) # check throttle timestamp = unclaimed_record.get('last_sent') if not throttle_period_expired(timestamp, throttle): raise HTTPError(http.BAD_REQUEST, data=dict( message_long='User account can only be claimed with an existing user once every 24 hours' )) # roll the valid token for each email, thus user cannot change email and approve a different email address verification_key = generate_verification_key(verification_type='claim') unclaimed_record['token'] = verification_key['token'] unclaimed_record['expires'] = verification_key['expires'] unclaimed_record['claimer_email'] = claimer.username unclaimed_user.save() referrer = User.load(unclaimed_record['referrer_id']) claim_url = web_url_for( 'claim_user_registered', uid=unclaimed_user._primary_key, pid=node._primary_key, token=unclaimed_record['token'], _external=True, ) # Send mail to referrer, telling them to forward verification link to claimer mails.send_mail( referrer.username, mails.FORWARD_INVITE_REGISTERED, user=unclaimed_user, referrer=referrer, node=node, claim_url=claim_url, fullname=unclaimed_record['name'], ) unclaimed_record['last_sent'] = get_timestamp() unclaimed_user.save() # Send mail to claimer, telling them to wait for referrer mails.send_mail( claimer.username, mails.PENDING_VERIFICATION_REGISTERED, fullname=claimer.fullname, referrer=referrer, node=node, )
def wrapper(node): from osf.models import OSFUser if node._id not in cache: osf_storage = node.get_addon('osfstorage') file_tree = osf_storage._get_file_tree(user=OSFUser.load( list(node.admin_contributor_or_group_member_ids)[0])) cache[node._id] = _do_get_file_map(file_tree) return func(node, cache[node._id])
def profile_view_id(uid, auth): user = OSFUser.load(uid) is_profile = auth and auth.user == user # Embed node data, so profile node lists can be rendered return _profile_view(user, is_profile, embed_nodes=False, include_node_counts=True)
def reset_password_post(uid=None, token=None): """ View for user to submit reset password form. HTTP Method: POST :param uid: the user id :param token: the token in verification key :return: :raises: HTTPError(http.BAD_REQUEST) if verification key for the user is invalid, has expired or was used """ form = ResetPasswordForm(request.form) # Check if request bears a valid pair of `uid` and `token` user_obj = OSFUser.load(uid) if not (user_obj and user_obj.verify_password_token(token=token)): error_data = { 'message_short': 'Invalid Request.', 'message_long': 'The requested URL is invalid, has expired, or was already used', } raise HTTPError(http.BAD_REQUEST, data=error_data) if not form.validate(): # Don't go anywhere forms.push_errors_to_status(form.errors) else: # clear verification key (v2) user_obj.verification_key_v2 = {} # new verification key (v1) for CAS user_obj.verification_key = generate_verification_key( verification_type=None) try: user_obj.set_password(form.password.data) user_obj.save() except exceptions.ChangePasswordError as error: for message in error.messages: status.push_status_message(message, kind='warning', trust=False) else: status.push_status_message('Password reset', kind='success', trust=False) # redirect to CAS and authenticate the user automatically with one-time verification key. return redirect( cas.get_login_url(web_url_for('user_account', _absolute=True), username=user_obj.username, verification_key=user_obj.verification_key)) return { 'uid': user_obj._id, 'token': user_obj.verification_key_v2['token'], }
def project_remove_contributor(auth, **kwargs): """Remove a contributor from a list of nodes. :param Auth auth: Consolidated authorization :raises: HTTPError(400) if contributors to be removed are not in list or if no admin users would remain after changes were applied """ contributor_id = request.get_json()['contributorID'] node_ids = request.get_json()['nodeIDs'] contributor = OSFUser.load(contributor_id) if contributor is None: raise HTTPError(http_status.HTTP_400_BAD_REQUEST, data={'message_long': 'Contributor not found.'}) redirect_url = {} parent_id = node_ids[0] for node_id in node_ids: # Update permissions and order node = AbstractNode.load(node_id) # Forbidden unless user is removing herself if not node.has_permission(auth.user, ADMIN): if auth.user != contributor: raise HTTPError(http_status.HTTP_403_FORBIDDEN) if node.visible_contributors.count() == 1 \ and node.visible_contributors[0] == contributor: raise HTTPError( http_status.HTTP_403_FORBIDDEN, data={ 'message_long': 'Must have at least one bibliographic contributor' }) nodes_removed = node.remove_contributor(contributor, auth=auth) # remove_contributor returns false if there is not one admin or visible contributor left after the move. if not nodes_removed: raise HTTPError( http_status.HTTP_400_BAD_REQUEST, data={'message_long': 'Could not remove contributor.'}) # On parent node, if user has removed herself from project, alert; redirect to # node summary if node is public, else to user's dashboard page if not node.is_contributor_or_group_member( auth.user) and node_id == parent_id: status.push_status_message( 'You have removed yourself as a contributor from this project', kind='success', trust=False, id='remove_self_contrib') if node.is_public: redirect_url = {'redirectUrl': node.url} else: redirect_url = {'redirectUrl': web_url_for('dashboard')} return redirect_url
def get_user_object(self, user_id, group): if user_id: user = OSFUser.load(user_id) if not user: raise exceptions.NotFound( detail='User with id {} not found.'.format(user_id)) if group.has_permission(user, 'member'): raise exceptions.ValidationError( detail='User is already a member of this group.') return user return user_id
def has_object_permission(self, request, view, obj): assert isinstance(obj, (AbstractNode, OSFUser, Contributor)), 'obj must be User, Contributor, or Node, got {}'.format(obj) auth = get_user_auth(request) context = request.parser_context['kwargs'] node = AbstractNode.load(context[view.node_lookup_url_kwarg]) user = OSFUser.load(context['user_id']) if request.method in permissions.SAFE_METHODS: return node.is_public or node.can_view(auth) elif request.method == 'DELETE': return node.has_permission(auth.user, osf_permissions.ADMIN) or auth.user == user else: return node.has_permission(auth.user, osf_permissions.ADMIN)
def has_object_permission(self, request, view, obj): assert_resource_type(obj, self.acceptable_models) auth = get_user_auth(request) context = request.parser_context['kwargs'] node = self.load_resource(context, view) user = OSFUser.load(context['user_id']) if request.method in permissions.SAFE_METHODS: return node.is_public or node.can_view(auth) elif request.method == 'DELETE': return node.has_permission(auth.user, osf_permissions.ADMIN) or auth.user == user else: return node.has_permission(auth.user, osf_permissions.ADMIN)
def save(self, request=None, *args, **kwargs): super(NodeSettings, self).save(*args, **kwargs) if request: if not hasattr(request, 'user'): # TODO: remove when Flask is removed _, user_id = get_request_and_user_id() user = OSFUser.load(user_id) else: user = request.user self.owner.check_spam(user, {'addons_forward_node_settings__url'}, get_headers_from_request(request))
def _validate_reports(value, *args, **kwargs): from osf.models import OSFUser for key, val in value.items(): if not OSFUser.load(key): raise ValidationValueError('Keys must be user IDs') if not isinstance(val, dict): raise ValidationTypeError('Values must be dictionaries') if ('category' not in val or 'text' not in val or 'date' not in val or 'retracted' not in val): raise ValidationValueError( ('Values must include `date`, `category`, ', '`text`, `retracted` keys') )
def _validate_reports(value, *args, **kwargs): from osf.models import OSFUser for key, val in value.iteritems(): if not OSFUser.load(key): raise ValidationValueError('Keys must be user IDs') if not isinstance(val, dict): raise ValidationTypeError('Values must be dictionaries') if ('category' not in val or 'text' not in val or 'date' not in val or 'retracted' not in val): raise ValidationValueError( ('Values must include `date`, `category`, ', '`text`, `retracted` keys') )
def has_object_permission(self, request, view, obj): assert isinstance(obj, (AbstractNode, User, Contributor)), 'obj must be User, Contributor, or Node, got {}'.format(obj) auth = get_user_auth(request) context = request.parser_context['kwargs'] node = AbstractNode.load(context[view.node_lookup_url_kwarg]) user = User.load(context['user_id']) if request.method in permissions.SAFE_METHODS: return node.is_public or node.can_view(auth) elif request.method == 'DELETE': return node.has_permission(auth.user, osf_permissions.ADMIN) or auth.user == user else: return node.has_permission(auth.user, osf_permissions.ADMIN)
def has_object_permission(self, request, view, obj): assert_resource_type(obj, self.acceptable_models) context = request.parser_context['kwargs'] preprint = self.load_resource(context, view) auth = get_user_auth(request) user = OSFUser.load(context['user_id']) if request.method in permissions.SAFE_METHODS: return super(ContributorDetailPermissions, self).has_object_permission(request, view, preprint) elif request.method == 'DELETE': return preprint.has_permission(auth.user, osf_permissions.ADMIN) or auth.user == user else: return preprint.has_permission(auth.user, osf_permissions.ADMIN)
def find_inactive_users_with_no_inactivity_email_sent_or_queued(): inactive_users = User.find( (Q('date_last_login', 'lt', timezone.now() - settings.NO_LOGIN_WAIT_TIME) & Q('tags__name', 'ne', 'osf4m')) | (Q('date_last_login', 'lt', timezone.now() - settings.NO_LOGIN_OSF4M_WAIT_TIME) & Q('tags__name', 'eq', 'osf4m')) ) inactive_emails = QueuedMail.find(Q('email_type', 'eq', NO_LOGIN_TYPE)) #This is done to prevent User query returns comparison to User, as equality fails #on datetime fields due to pymongo rounding. Instead here _id is compared. users_sent_id = [email.user._id for email in inactive_emails] inactive_ids = [user._id for user in inactive_users if user.is_active] users_to_send = [User.load(id) for id in (set(inactive_ids) - set(users_sent_id))] return users_to_send
def reviews_submit_notification(self, context): event_type = utils.find_subscription_type('global_reviews') for user_id in context['email_recipients']: user = OSFUser.load(user_id) user_subscriptions = get_user_subscriptions(user, event_type) context['no_future_emails'] = user_subscriptions['none'] context['is_creator'] = user == context['reviewable'].node.creator context['provider_name'] = context['reviewable'].provider.name mails.send_mail(user.username, mails.REVIEWS_SUBMISSION_CONFIRMATION, mimetype='html', user=user, **context)
def claim_user_post(node, **kwargs): """ View for claiming a user from the X-editable form on a project page. :param node: the project node :return: """ request_data = request.json # The unclaimed user unclaimed_user = OSFUser.load(request_data['pk']) unclaimed_data = unclaimed_user.get_unclaimed_record(node._primary_key) # Claimer is not logged in and submit her/his email through X-editable, stored in `request_data['value']` if 'value' in request_data: email = request_data['value'].lower().strip() claimer = get_user(email=email) # registered user if claimer and claimer.is_registered: send_claim_registered_email(claimer, unclaimed_user, node) # unregistered user else: send_claim_email(email, unclaimed_user, node, notify=True) # Claimer is logged in with confirmed identity stored in `request_data['claimerId']` elif 'claimerId' in request_data: claimer_id = request_data['claimerId'] claimer = OSFUser.load(claimer_id) send_claim_registered_email(claimer, unclaimed_user, node) email = claimer.username else: raise HTTPError(http_status.HTTP_400_BAD_REQUEST) return { 'status': 'success', 'email': email, 'fullname': unclaimed_data['name'] }
def claim_user_post(node, **kwargs): """ View for claiming a user from the X-editable form on a project page. :param node: the project node :return: """ request_data = request.json # The unclaimed user unclaimed_user = OSFUser.load(request_data['pk']) unclaimed_data = unclaimed_user.get_unclaimed_record(node._primary_key) # Claimer is not logged in and submit her/his email through X-editable, stored in `request_data['value']` if 'value' in request_data: email = request_data['value'].lower().strip() claimer = get_user(email=email) # registered user if claimer and claimer.is_registered: send_claim_registered_email(claimer, unclaimed_user, node) # unregistered user else: send_claim_email(email, unclaimed_user, node, notify=True) # Claimer is logged in with confirmed identity stored in `request_data['claimerId']` elif 'claimerId' in request_data: claimer_id = request_data['claimerId'] claimer = OSFUser.load(claimer_id) send_claim_registered_email(claimer, unclaimed_user, node) email = claimer.username else: raise HTTPError(http.BAD_REQUEST) return { 'status': 'success', 'email': email, 'fullname': unclaimed_data['name'] }
def has_object_permission(self, request, view, obj): if not isinstance(obj, OSFGroup): obj = OSFGroup.load(request.parser_context['kwargs']['group_id']) assert_resource_type(obj, self.acceptable_models) auth = get_user_auth(request) if request.method in permissions.SAFE_METHODS: return True elif request.method == 'DELETE': user = OSFUser.load(request.parser_context['kwargs']['user_id']) # You must have manage permissions on the OSFGroup to remove a member, # unless you are removing yourself return obj.has_permission(auth.user, MANAGE) or auth.user == user else: return auth.user and obj.has_permission(auth.user, MANAGE)
def wrapped(*args, **kwargs): from osf.models import OSFUser user = OSFUser.load(kwargs['uid']) if user is not None: if user.is_confirmed: return func(*args, **kwargs) else: raise HTTPError(httplib.BAD_REQUEST, data={ 'message_short': 'Account not yet confirmed', 'message_long': 'The profile page could not be displayed as the user has not confirmed the account.' }) else: raise HTTPError(httplib.NOT_FOUND)
def project_remove_contributor(auth, **kwargs): """Remove a contributor from a list of nodes. :param Auth auth: Consolidated authorization :raises: HTTPError(400) if contributors to be removed are not in list or if no admin users would remain after changes were applied """ contributor_id = request.get_json()['contributorID'] node_ids = request.get_json()['nodeIDs'] contributor = OSFUser.load(contributor_id) if contributor is None: raise HTTPError(http.BAD_REQUEST, data={'message_long': 'Contributor not found.'}) redirect_url = {} parent_id = node_ids[0] for node_id in node_ids: # Update permissions and order node = AbstractNode.load(node_id) # Forbidden unless user is removing herself if not node.has_permission(auth.user, 'admin'): if auth.user != contributor: raise HTTPError(http.FORBIDDEN) if node.visible_contributors.count() == 1 \ and node.visible_contributors[0] == contributor: raise HTTPError(http.FORBIDDEN, data={ 'message_long': 'Must have at least one bibliographic contributor' }) nodes_removed = node.remove_contributor(contributor, auth=auth) # remove_contributor returns false if there is not one admin or visible contributor left after the move. if not nodes_removed: raise HTTPError(http.BAD_REQUEST, data={ 'message_long': 'Could not remove contributor.'}) # On parent node, if user has removed herself from project, alert; redirect to # node summary if node is public, else to user's dashboard page if not node.is_contributor(auth.user) and node_id == parent_id: status.push_status_message( 'You have removed yourself as a contributor from this project', kind='success', trust=False, id='remove_self_contrib' ) if node.is_public: redirect_url = {'redirectUrl': node.url} else: redirect_url = {'redirectUrl': web_url_for('dashboard')} return redirect_url
def reset_password_post(uid=None, token=None): """ View for user to submit reset password form. HTTP Method: POST :param uid: the user id :param token: the token in verification key :return: :raises: HTTPError(http.BAD_REQUEST) if verification key for the user is invalid, has expired or was used """ form = ResetPasswordForm(request.form) # Check if request bears a valid pair of `uid` and `token` user_obj = OSFUser.load(uid) if not (user_obj and user_obj.verify_password_token(token=token)): error_data = { 'message_short': 'Invalid Request.', 'message_long': 'The requested URL is invalid, has expired, or was already used', } raise HTTPError(http.BAD_REQUEST, data=error_data) if not form.validate(): # Don't go anywhere forms.push_errors_to_status(form.errors) else: # clear verification key (v2) user_obj.verification_key_v2 = {} # new verification key (v1) for CAS user_obj.verification_key = generate_verification_key(verification_type=None) try: user_obj.set_password(form.password.data) user_obj.save() except exceptions.ChangePasswordError as error: for message in error.messages: status.push_status_message(message, kind='warning', trust=False) else: status.push_status_message('Password reset', kind='success', trust=False) # redirect to CAS and authenticate the user automatically with one-time verification key. return redirect(cas.get_login_url( web_url_for('user_account', _absolute=True), username=user_obj.username, verification_key=user_obj.verification_key )) return { 'uid': user_obj._id, 'token': user_obj.verification_key_v2['token'], }
def store_emails(recipient_ids, notification_type, event, user, node, timestamp, abstract_provider=None, template=None, **context): """Store notification emails Emails are sent via celery beat as digests :param recipient_ids: List of user ids to send mail to. :param notification_type: from constants.Notification_types :param event: event that triggered notification :param user: user who triggered the notification :param node: instance of Node :param timestamp: time event happened :param context: :return: -- """ if notification_type == 'none': return # If `template` is not specified, default to using a template with name `event` template = '{template}.html.mako'.format(template=template or event) # user whose action triggered email sending context['user'] = user node_lineage_ids = get_node_lineage(node) if node else [] for recipient_id in recipient_ids: if recipient_id == user._id: continue recipient = OSFUser.load(recipient_id) if recipient.is_disabled: continue context['localized_timestamp'] = localize_timestamp( timestamp, recipient) context['recipient'] = recipient message = mails.render_message(template, **context) digest = NotificationDigest(timestamp=timestamp, send_type=notification_type, event=event, user=recipient, message=message, node_lineage=node_lineage_ids, provider=abstract_provider) digest.save()
def _get_current_user(): from osf.models import OSFUser from framework.auth import cas current_user_id = get_current_user_id() header_token = request.headers.get('Authorization', None) if current_user_id: return OSFUser.load(current_user_id, select_for_update=check_select_for_update(request)) elif header_token and 'bearer' in header_token.lower(): # instead of querying directly here, let CAS deal with the authentication client = cas.get_client() auth_token = cas.parse_auth_header(header_token) try: cas_auth_response = client.profile(auth_token) except cas.CasHTTPError: return None return OSFUser.load(cas_auth_response.user, select_for_update=check_select_for_update(request) ) if cas_auth_response.authenticated else None else: return None
def spam_check(self): # Since wiki_pages_current will be removed from Node model, when a new WikiVersion is saved, trigger a spam check. request, user_id = get_request_and_user_id() request_headers = {} if not isinstance(request, DummyRequest): request_headers = { k: v for k, v in get_headers_from_request(request).items() if isinstance(v, basestring) } user = OSFUser.load(user_id) if user: return self.wiki_page.node.check_spam(user, ['wiki_pages_latest'], request_headers) else: return False
def notify_mentions(event, user, node, timestamp, **context): event_type = utils.find_subscription_type(event) sent_users = [] new_mentions = context.get('new_mentions', []) for m in new_mentions: mentioned_user = OSFUser.load(m) subscriptions = get_user_subscriptions(mentioned_user, event_type) for notification_type in subscriptions: if (notification_type != 'none' and subscriptions[notification_type] and m in subscriptions[notification_type]): store_emails([m], notification_type, 'mentions', user, node, timestamp, **context) sent_users.extend([m]) return sent_users
def replace_unclaimed_user_with_registered(user): """Listens for the user_registered signal. If unreg_user is stored in the session, then the current user is trying to claim themselves as a contributor. Replaces the old, unregistered contributor with the newly registered account. """ unreg_user_info = session.data.get('unreg_user') if unreg_user_info: unreg_user = OSFUser.load(unreg_user_info['uid']) pid = unreg_user_info['pid'] node = AbstractNode.load(pid) node.replace_contributor(old=unreg_user, new=user) node.save() status.push_status_message( 'Successfully claimed contributor.', kind='success', trust=False)
def wrapped(payload, *args, **kwargs): try: user = OSFUser.load(payload['user']) dest_node = AbstractNode.load(payload['destination']['node']) source = OsfStorageFileNode.get(payload['source'], kwargs['node']) dest_parent = OsfStorageFolder.get(payload['destination']['parent'], dest_node) kwargs.update({ 'user': user, 'source': source, 'destination': dest_parent, 'name': payload['destination']['name'], }) except KeyError: raise HTTPError(httplib.BAD_REQUEST) return func(*args, **kwargs)
def verify_merge(merged_list): """ Expecting merged list in format [{"user_id": "abcde", "preprints": ["12345"]}] """ for user_dict in merged_list: user = OSFUser.load(user_dict['user_id']) merged_by = user.merged_by for preprint_id in user_dict['preprints']: preprint = Preprint.load(preprint_id) user_contrib = _get_preprint_contributor(preprint, user) merged_by_contrib = _get_preprint_contributor(preprint, merged_by) print 'Preprint: {}'.format(preprint._id) print ' User: {}, Merged by: {}'.format(user._id, merged_by._id) print ' Perms: {}, {}'.format(user_contrib.permission if user_contrib else None, merged_by_contrib.permission if merged_by_contrib else None) print ' Bib: {}, {}'.format(user_contrib.visible if user_contrib else None, merged_by_contrib.visible if merged_by_contrib else None) print ' Creator: {}, {}'.format(preprint.creator == user, preprint.creator == merged_by) print '_______________________________________'