Exemplo n.º 1
0
def user_account_password(auth, **kwargs):
    user = auth.user
    old_password = request.form.get('old_password', None)
    new_password = request.form.get('new_password', None)
    confirm_password = request.form.get('confirm_password', None)

    # It has been more than 1 hour since last invalid attempt to change password. Reset the counter for invalid attempts.
    if throttle_period_expired(user.change_password_last_attempt, settings.TIME_RESET_CHANGE_PASSWORD_ATTEMPTS):
        user.reset_old_password_invalid_attempts()

    # There have been more than 3 failed attempts and throttle hasn't expired.
    if user.old_password_invalid_attempts >= settings.INCORRECT_PASSWORD_ATTEMPTS_ALLOWED and not throttle_period_expired(user.change_password_last_attempt, settings.CHANGE_PASSWORD_THROTTLE):
        push_status_message(
            message='Too many failed attempts. Please wait a while before attempting to change your password.',
            kind='warning',
            trust=False
        )
        return redirect(web_url_for('user_account'))

    try:
        user.change_password(old_password, new_password, confirm_password)
    except ChangePasswordError as error:
        for m in error.messages:
            push_status_message(m, kind='warning', trust=False)
    else:
        # We have to logout the user first so all CAS sessions are invalid
        user.save()
        osf_logout()
        return redirect(cas.get_logout_url(cas.get_login_url(
            web_url_for('user_account', _absolute=True) + '?password_reset=True',
            username=user.username,
            verification_key=user.verification_key,
        )))
    user.save()
    return redirect(web_url_for('user_account'))
Exemplo n.º 2
0
def auth_login(auth, **kwargs):
    """If GET request, show login page. If POST, attempt to log user in if
    login form passsed; else send forgot password email.

    """
    campaign = request.args.get('campaign')
    next_url = request.args.get('next')
    if campaign:
        next_url = campaigns.campaign_url_for(campaign)
    if auth.logged_in:
        if not request.args.get('logout'):
            if next_url:
                return redirect(next_url)
            return redirect(web_url_for('dashboard'))
        # redirect user to CAS for logout, return here w/o authentication
        return auth_logout(redirect_url=request.url)
    if kwargs.get('first', False):
        status.push_status_message('You may now log in', 'info')

    status_message = request.args.get('status', '')
    if status_message == 'expired':
        status.push_status_message('The private link you used is expired.')

    if next_url:
        status.push_status_message(language.MUST_LOGIN)
    # set login_url to form action, upon successful authentication specifically w/o logout=True,
    # allows for next to be followed or a redirect to the dashboard.
    redirect_url = web_url_for('auth_login', next=next_url, _absolute=True)

    data = {}
    if campaign and campaign in campaigns.CAMPAIGNS:
        data['campaign'] = campaign
    data['login_url'] = cas.get_login_url(redirect_url, auto=True)

    return data, http.OK
Exemplo n.º 3
0
 def test_get_revisions(self):
     with patch_client('website.addons.dropbox.views.crud.get_node_addon_client'):
         path = 'foo.rst'
         url = self.project.api_url_for('dropbox_get_revisions', path=path)
         res = self.app.get(url, auth=self.user.auth)
         json_data = res.json
         result = json_data['result']
         expected = [
             rev for rev in mock_responses['revisions']
             if not rev.get('is_deleted')
         ]
         assert_equal(len(result), len(expected))
         for each in result:
             download_link = each['download']
             assert_equal(
                 download_link,
                 web_url_for(
                     'dropbox_download',
                     pid=self.project._primary_key,
                     path=path, rev=each['rev']
                 )
             )
             view_link = each['view']
             assert_equal(
                 view_link,
                 web_url_for(
                     'dropbox_view_file',
                     pid=self.project._primary_key,
                     path=path, rev=each['rev']
                 )
             )
Exemplo n.º 4
0
def auth_login(auth, **kwargs):
    """If GET request, show login page. If POST, attempt to log user in if
    login form passsed; else send forgot password email.

    """
    next_url = request.args.get('next')
    if auth.logged_in:
        if not request.args.get('logout'):
            if next_url:
                return redirect(next_url)
            return redirect(web_url_for('dashboard'))
        # redirect user to CAS for logout, return here w/o authentication
        return auth_logout(redirect_url=request.url)
    if kwargs.get('first', False):
        status.push_status_message('You may now log in', 'info')

    status_message = request.args.get('status', '')
    if status_message == 'expired':
        status.push_status_message('The private link you used is expired.')

    code = http.OK
    if next_url:
        status.push_status_message(language.MUST_LOGIN)
        # Don't raise error if user is being logged out
        if not request.args.get('logout'):
            code = http.UNAUTHORIZED
    # set login_url to form action, upon successful authentication specifically w/o logout=True,
    # allows for next to be followed or a redirect to the dashboard.
    redirect_url = web_url_for('auth_login', next=next_url, _absolute=True)
    login_url = cas.get_login_url(redirect_url, auto=True)
    return {'login_url': login_url}, code
Exemplo n.º 5
0
def user_account_password(auth, **kwargs):
    user = auth.user
    old_password = request.form.get('old_password', None)
    new_password = request.form.get('new_password', None)
    confirm_password = request.form.get('confirm_password', None)

    # It has been more than 1 hour since last invalid attempt to change password. Reset the counter for invalid attempts.
    if throttle_period_expired(user.change_password_last_attempt, settings.TIME_RESET_CHANGE_PASSWORD_ATTEMPTS):
        user.reset_old_password_invalid_attempts()

    # There have been more than 3 failed attempts and throttle hasn't expired.
    if user.old_password_invalid_attempts >= settings.INCORRECT_PASSWORD_ATTEMPTS_ALLOWED and not throttle_period_expired(user.change_password_last_attempt, settings.CHANGE_PASSWORD_THROTTLE):
        push_status_message(
            message='Too many failed attempts. Please wait a while before attempting to change your password.',
            kind='warning',
            trust=False
        )
        return redirect(web_url_for('user_account'))

    try:
        user.change_password(old_password, new_password, confirm_password)
        if user.verification_key_v2:
            user.verification_key_v2['expires'] = timezone.now()
        user.save()
    except ChangePasswordError as error:
        for m in error.messages:
            push_status_message(m, kind='warning', trust=False)
    else:
        push_status_message('Password updated successfully.', kind='success', trust=False)

    user.save()

    return redirect(web_url_for('user_account'))
Exemplo n.º 6
0
def confirm_email_get(token, auth=None, **kwargs):
    """View for email confirmation links.
    Authenticates and redirects to user settings page if confirmation is
    successful, otherwise shows an "Expired Link" error.

    methods: GET
    """
    user = User.load(kwargs['uid'])
    is_merge = 'confirm_merge' in request.args
    is_initial_confirmation = not user.date_confirmed

    if user is None:
        raise HTTPError(http.NOT_FOUND)

    if auth and auth.user and (auth.user._id == user._id or auth.user._id == user.merged_by._id):
        if not is_merge:
            # determine if the user registered through a campaign
            campaign = campaigns.campaign_for_user(user)
            if campaign:
                return redirect(
                    campaigns.campaign_url_for(campaign)
                )
            status.push_status_message(language.WELCOME_MESSAGE, 'default', jumbotron=True)
            # Go to dashboard
            return redirect(web_url_for('dashboard'))

        status.push_status_message(language.MERGE_COMPLETE, 'success')
        return redirect(web_url_for('user_account'))

    try:
        user.confirm_email(token, merge=is_merge)
    except exceptions.EmailConfirmTokenError as e:
        raise HTTPError(http.BAD_REQUEST, data={
            'message_short': e.message_short,
            'message_long': e.message_long
        })

    if is_initial_confirmation:
        user.date_last_login = datetime.datetime.utcnow()
        user.save()

        # Send out our welcome message
        mails.send_mail(
            to_addr=user.username,
            mail=mails.WELCOME,
            mimetype='html',
            user=user
        )

    # Redirect to CAS and authenticate the user with a verification key.
    user.verification_key = security.random_string(20)
    user.save()

    return redirect(cas.get_login_url(
        request.url,
        auto=True,
        username=user.username,
        verification_key=user.verification_key
    ))
Exemplo n.º 7
0
 def setUp(self):
     super(TestForgotPassword, self).setUp()
     self.user = UserFactory()
     self.auth_user = AuthUserFactory()
     self.get_url = web_url_for('forgot_password_get')
     self.post_url = web_url_for('forgot_password_post')
     self.user.verification_key_v2 = {}
     self.user.save()
Exemplo n.º 8
0
 def test_web_url_for_guid_case_sensitive(self):
     with self.app.test_request_context():
         # check /project/<pid>
         assert_equal('/ABCdef/', web_url_for('dummy_guid_project_view', pid='ABCdef', _guid=True))
         # check /project/<pid>/node/<nid>
         assert_equal('/GHIjkl/', web_url_for('dummy_guid_project_view', pid='ABCdef', nid='GHIjkl', _guid=True))
         # check /profile/<pid>
         assert_equal('/MNOpqr/', web_url_for('dummy_guid_profile_view', pid='MNOpqr', _guid=True))
Exemplo n.º 9
0
def claim_user_registered(auth, node, **kwargs):
    """View that prompts user to enter their password in order to claim
    contributorship on a project.

    A user must be logged in.
    """
    current_user = auth.user

    sign_out_url = web_url_for('auth_login', logout=True, next=request.url)
    if not current_user:
        return redirect(sign_out_url)
    # Logged in user should not be a contributor the project
    if node.is_contributor(current_user):
        logout_url = web_url_for('auth_logout', redirect_url=request.url)
        data = {
            'message_short': 'Already a contributor',
            'message_long': ('The logged-in user is already a contributor to this '
                'project. Would you like to <a href="{}">log out</a>?').format(logout_url)
        }
        raise HTTPError(http.BAD_REQUEST, data=data)
    uid, pid, token = kwargs['uid'], kwargs['pid'], kwargs['token']
    unreg_user = User.load(uid)
    if not verify_claim_token(unreg_user, token, pid=node._primary_key):
        raise HTTPError(http.BAD_REQUEST)

    # Store the unreg_user data on the session in case the user registers
    # a new account
    session.data['unreg_user'] = {
        'uid': uid, 'pid': pid, 'token': token
    }

    form = PasswordForm(request.form)
    if request.method == 'POST':
        if form.validate():
            if current_user.check_password(form.password.data):
                node.replace_contributor(old=unreg_user, new=current_user)
                node.save()
                status.push_status_message(
                    'You are now a contributor to this project.',
                    kind='success',
                    trust=False
                )
                return redirect(node.url)
            else:
                status.push_status_message(language.LOGIN_FAILED, kind='warning', trust=False)
        else:
            forms.push_errors_to_status(form.errors)
    if is_json_request():
        form_ret = forms.utils.jsonify(form)
        user_ret = profile_utils.serialize_user(current_user, full=False)
    else:
        form_ret = form
        user_ret = current_user
    return {
        'form': form_ret,
        'user': user_ret,
        'signOutUrl': sign_out_url
    }
Exemplo n.º 10
0
def box_oauth_finish(auth, **kwargs):
    """View called when the Oauth flow is completed. Adds a new BoxUserSettings
    record to the user and saves the user's access token and account info.
    """
    user = auth.user
    node = Node.load(session.data.pop('box_auth_nid', None))

    # Handle request cancellations from Box's API
    if request.args.get('error'):
        flash('Box authorization request cancelled.')
        if node:
            return redirect(node.web_url_for('node_setting'))
        return redirect(web_url_for('user_addons'))

    result = finish_auth()

    # If result is a redirect response, follow the redirect
    if isinstance(result, BaseResponse):
        return result

    client = BoxClient(CredentialsV2(
        result['access_token'],
        result['refresh_token'],
        settings.BOX_KEY,
        settings.BOX_SECRET,
    ))

    about = client.get_user_info()
    oauth_settings = BoxOAuthSettings.load(about['id'])

    if not oauth_settings:
        oauth_settings = BoxOAuthSettings(user_id=about['id'], username=about['name'])
        oauth_settings.save()

    oauth_settings.refresh_token = result['refresh_token']
    oauth_settings.access_token = result['access_token']
    oauth_settings.expires_at = datetime.utcfromtimestamp(time.time() + 3600)

    # Make sure user has box enabled
    user.add_addon('box')
    user.save()

    user_settings = user.get_addon('box')
    user_settings.oauth_settings = oauth_settings

    user_settings.save()

    flash('Successfully authorized Box', 'success')

    if node:
        # Automatically use newly-created auth
        if node.has_addon('box'):
            node_addon = node.get_addon('box')
            node_addon.set_user_auth(user_settings)
            node_addon.save()
        return redirect(node.web_url_for('node_setting'))
    return redirect(web_url_for('user_addons'))
Exemplo n.º 11
0
def claim_user_form(auth, **kwargs):
    """
    View for rendering the set password page for a claimed user.
    Must have ``token`` as a querystring argument.
    Renders the set password form, validates it, and sets the user's password.
    HTTP Method: GET, POST
    """

    uid, pid = kwargs['uid'], kwargs['pid']
    token = request.form.get('token') or request.args.get('token')
    user = User.load(uid)

    # If unregistered user is not in database, or url bears an invalid token raise HTTP 400 error
    if not user or not verify_claim_token(user, token, pid):
        error_data = {
            'message_short': 'Invalid url.',
            'message_long': 'Claim user does not exists, the token in the URL is invalid or has expired.'
        }
        raise HTTPError(http.BAD_REQUEST, data=error_data)

    # If user is logged in, redirect to 're-enter password' page
    if auth.logged_in:
        return redirect(web_url_for('claim_user_registered',
            uid=uid, pid=pid, token=token))

    unclaimed_record = user.unclaimed_records[pid]
    user.fullname = unclaimed_record['name']
    user.update_guessed_names()
    # The email can be the original referrer email if no claimer email has been specified.
    claimer_email = unclaimed_record.get('claimer_email') or unclaimed_record.get('email')
    form = SetEmailAndPasswordForm(request.form, token=token)

    if request.method == 'POST':
        if form.validate():
            username, password = claimer_email, form.password.data
            user.register(username=username, password=password)
            # Clear unclaimed records
            user.unclaimed_records = {}
            user.verification_key = generate_verification_key()
            user.save()
            # Authenticate user and redirect to project page
            status.push_status_message(language.CLAIMED_CONTRIBUTOR, kind='success', trust=True)
            # Redirect to CAS and authenticate the user with a verification key.
            return redirect(cas.get_login_url(
                web_url_for('view_project', pid=pid, _absolute=True),
                username=user.username,
                verification_key=user.verification_key
            ))
        else:
            forms.push_errors_to_status(form.errors)

    return {
        'firstname': user.given_name,
        'email': claimer_email if claimer_email else '',
        'fullname': user.fullname,
        'form': forms.utils.jsonify(form) if is_json_request() else form,
    }
Exemplo n.º 12
0
def claim_user_form(auth, **kwargs):
    """View for rendering the set password page for a claimed user.

    Must have ``token`` as a querystring argument.

    Renders the set password form, validates it, and sets the user's password.
    """
    uid, pid = kwargs['uid'], kwargs['pid']
    token = request.form.get('token') or request.args.get('token')

    # If user is logged in, redirect to 're-enter password' page
    if auth.logged_in:
        return redirect(web_url_for('claim_user_registered',
            uid=uid, pid=pid, token=token))

    user = User.load(uid)  # The unregistered user
    # user ID is invalid. Unregistered user is not in database
    if not user:
        raise HTTPError(http.BAD_REQUEST)
    # If claim token not valid, redirect to registration page
    if not verify_claim_token(user, token, pid):
        return redirect(web_url_for('auth_login'))
    unclaimed_record = user.unclaimed_records[pid]
    user.fullname = unclaimed_record['name']
    user.update_guessed_names()
    # The email can be the original referrer email if no claimer email has been specified.
    claimer_email = unclaimed_record.get('claimer_email') or unclaimed_record.get('email')
    form = SetEmailAndPasswordForm(request.form, token=token)
    if request.method == 'POST':
        if form.validate():
            username, password = claimer_email, form.password.data
            user.register(username=username, password=password)
            # Clear unclaimed records
            user.unclaimed_records = {}
            user.verification_key = security.random_string(20)
            user.save()
            # Authenticate user and redirect to project page
            node = Node.load(pid)
            status.push_status_message(language.CLAIMED_CONTRIBUTOR.format(node=node),
                                       kind='success',
                                       trust=True)
            # Redirect to CAS and authenticate the user with a verification key.
            return redirect(cas.get_login_url(
                web_url_for('user_profile', _absolute=True),
                auto=True,
                username=user.username,
                verification_key=user.verification_key
            ))
        else:
            forms.push_errors_to_status(form.errors)
    return {
        'firstname': user.given_name,
        'email': claimer_email if claimer_email else '',
        'fullname': user.fullname,
        'form': forms.utils.jsonify(form) if is_json_request() else form,
    }
Exemplo n.º 13
0
def claim_user_registered(auth, node, **kwargs):
    """
    View that prompts user to enter their password in order to claim being a contributor on a project.
    A user must be logged in.
    """

    current_user = auth.user

    sign_out_url = web_url_for("auth_register", logout=True, next=request.url)
    if not current_user:
        return redirect(sign_out_url)

    # Logged in user should not be a contributor the project
    if node.is_contributor(current_user):
        logout_url = web_url_for("auth_logout", redirect_url=request.url)
        data = {
            "message_short": "Already a contributor",
            "message_long": (
                "The logged-in user is already a contributor to this "
                'project. Would you like to <a href="{}">log out</a>?'
            ).format(logout_url),
        }
        raise HTTPError(http.BAD_REQUEST, data=data)

    uid, pid, token = kwargs["uid"], kwargs["pid"], kwargs["token"]
    unreg_user = User.load(uid)
    if not verify_claim_token(unreg_user, token, pid=node._primary_key):
        error_data = {
            "message_short": "Invalid url.",
            "message_long": "The token in the URL is invalid or has expired.",
        }
        raise HTTPError(http.BAD_REQUEST, data=error_data)

    # Store the unreg_user data on the session in case the user registers
    # a new account
    session.data["unreg_user"] = {"uid": uid, "pid": pid, "token": token}

    form = PasswordForm(request.form)
    if request.method == "POST":
        if form.validate():
            if current_user.check_password(form.password.data):
                node.replace_contributor(old=unreg_user, new=current_user)
                node.save()
                status.push_status_message("You are now a contributor to this project.", kind="success", trust=False)
                return redirect(node.url)
            else:
                status.push_status_message(language.LOGIN_FAILED, kind="warning", trust=False)
        else:
            forms.push_errors_to_status(form.errors)
    if is_json_request():
        form_ret = forms.utils.jsonify(form)
        user_ret = profile_utils.serialize_user(current_user, full=False)
    else:
        form_ret = form
        user_ret = current_user
    return {"form": form_ret, "user": user_ret, "signOutUrl": sign_out_url}
Exemplo n.º 14
0
 def setUp(self):
     super(TestResetPassword, self).setUp()
     self.user = AuthUserFactory()
     self.another_user = AuthUserFactory()
     self.osf_key = generate_verification_key()
     self.user.verification_key = self.osf_key
     self.user.save()
     self.cas_key = None
     self.get_url = web_url_for('reset_password_get', verification_key=self.osf_key)
     self.get_url_invalid_key = web_url_for('reset_password_get', verification_key=generate_verification_key())
Exemplo n.º 15
0
def googledrive_oauth_finish(auth, **kwargs):
    """View called when the Oauth flow is completed. Adds a new GoogleDriveUserSettings
    record to the user and saves the user's access token and account info.
    """
    user = auth.user
    node = Node.load(session.data.pop('googledrive_auth_nid', None))

    # Handle request cancellations from Google's API
    if request.args.get('error'):
        flash('Google Drive authorization request cancelled.')
        if node:
            return redirect(node.web_url_for('node_setting'))
        return redirect(web_url_for('user_addons'))

    user.add_addon('googledrive')
    user.save()

    code = request.args.get('code')
    user_settings = user.get_addon('googledrive')

    state = session.data.pop('googledrive_auth_state')
    if state != request.args.get('state'):
        raise HTTPError(http.BAD_REQUEST)

    if code is None:
        raise HTTPError(http.BAD_REQUEST)

    auth_client = GoogleAuthClient()
    token = auth_client.finish(code)
    info = auth_client.userinfo(token['access_token'])

    # Attempt to attach an existing oauth settings model
    oauth_settings = GoogleDriveOAuthSettings.load(info['sub'])
    # Create a new oauth settings model
    if not oauth_settings:
        oauth_settings = GoogleDriveOAuthSettings()
        oauth_settings.user_id = info['sub']
        oauth_settings.save()
    user_settings.oauth_settings = oauth_settings

    user_settings.username = info['name']
    user_settings.access_token = token['access_token']
    user_settings.refresh_token = token['refresh_token']
    user_settings.expires_at = datetime.utcfromtimestamp(token['expires_at'])
    user_settings.save()

    flash('Successfully authorized Google Drive', 'success')
    if node:
        if node.has_addon('googledrive'):
            node_addon = node.get_addon('googledrive')
            node_addon.set_user_auth(user_settings)
            node_addon.save()
        return redirect(node.web_url_for('node_setting'))
    return redirect(web_url_for('user_addons'))
Exemplo n.º 16
0
def auth_login(auth, **kwargs):
    """If GET request, show login page. If POST, attempt to log user in if
    login form passsed; else send forgot password email.

    """
    campaign = request.args.get('campaign')
    next_url = request.args.get('next')
    must_login_warning = True

    if campaign:
        next_url = campaigns.campaign_url_for(campaign)

    if not next_url:
        next_url = request.args.get('redirect_url')
        must_login_warning = False

    if next_url:
        # Only allow redirects which are relative root or full domain, disallows external redirects.
        if not (next_url[0] == '/' or next_url.startsWith(settings.DOMAIN)):
            raise HTTPError(http.InvalidURL)

    if auth.logged_in:
        if not request.args.get('logout'):
            if next_url:
                return redirect(next_url)
            return redirect(web_url_for('dashboard'))
        # redirect user to CAS for logout, return here w/o authentication
        return auth_logout(redirect_url=request.url)
    if kwargs.get('first', False):
        status.push_status_message('You may now log in', 'info')

    status_message = request.args.get('status', '')
    if status_message == 'expired':
        status.push_status_message('The private link you used is expired.')

    if next_url and must_login_warning:
        status.push_status_message(language.MUST_LOGIN)
    # set login_url to form action, upon successful authentication specifically w/o logout=True,
    # allows for next to be followed or a redirect to the dashboard.
    redirect_url = web_url_for('auth_login', next=next_url, _absolute=True)

    data = {}
    if campaign and campaign in campaigns.CAMPAIGNS:
        if (campaign == 'institution' and settings.ENABLE_INSTITUTIONS) or campaign != 'institution':
            data['campaign'] = campaign
    data['login_url'] = cas.get_login_url(redirect_url, auto=True)
    data['institution_redirect'] = cas.get_institution_target(redirect_url)
    data['redirect_url'] = next_url

    data['sign_up'] = request.args.get('sign_up', False)

    return data, http.OK
Exemplo n.º 17
0
def add_poster_by_email(conference, message):
    """
    :param Conference conference:
    :param ConferenceMessage message:
    """
    # Fail if no attachments
    if not message.attachments:
        return send_mail(message.sender_email, CONFERENCE_FAILED, fullname=message.sender_display)

    created = []

    with TokuTransaction():
        user, user_created = utils.get_or_create_user(message.sender_display, message.sender_email, message.is_spam)
        if user_created:
            created.append(user)
            set_password_url = web_url_for("reset_password", verification_key=user.verification_key, _absolute=True)
        else:
            set_password_url = None

        node, node_created = utils.get_or_create_node(message.subject, user)
        if node_created:
            created.append(node)

        utils.provision_node(conference, message, node, user)
        utils.record_message(message, created)

    utils.upload_attachments(user, node, message.attachments)

    download_url = node.web_url_for(
        "addon_view_or_download_file",
        path=message.attachments[0].filename,
        provider="osfstorage",
        action="download",
        _absolute=True,
    )

    # Send confirmation email
    send_mail(
        message.sender_email,
        CONFERENCE_SUBMITTED,
        conf_full_name=conference.name,
        conf_view_url=web_url_for("conference_results", meeting=message.conference_name, _absolute=True),
        fullname=message.sender_display,
        user_created=user_created,
        set_password_url=set_password_url,
        profile_url=user.absolute_url,
        node_url=node.absolute_url,
        file_url=download_url,
        presentation_type=message.conference_category.lower(),
        is_spam=message.is_spam,
    )
Exemplo n.º 18
0
def forgot_password_post(auth, **kwargs):
    """
    View for user to submit forgot password form.
    HTTP Method: POST
    """

    # If user is already logged in, redirect to dashboard page.
    if auth.logged_in:
        return redirect(web_url_for('dashboard'))

    form = ForgotPasswordForm(request.form, prefix='forgot_password')

    if form.validate():
        email = form.email.data
        status_message = ('If there is an OSF account associated with {0}, an email with instructions on how to '
                          'reset the OSF password has been sent to {0}. If you do not receive an email and believe '
                          'you should have, please contact OSF Support. ').format(email)
        # check if the user exists
        user_obj = get_user(email=email)
        if user_obj:
            # check forgot_password rate limit
            if throttle_period_expired(user_obj.email_last_sent, settings.SEND_EMAIL_THROTTLE):
                # new random verification key, allows OSF to check whether the reset_password request is valid,
                # this verification key is used twice, one for GET reset_password and one for POST reset_password
                # and it will be destroyed when POST reset_password succeeds
                user_obj.verification_key = generate_verification_key()
                user_obj.email_last_sent = datetime.datetime.utcnow()
                user_obj.save()
                reset_link = furl.urljoin(
                    settings.DOMAIN,
                    web_url_for(
                        'reset_password_get',
                        verification_key=user_obj.verification_key
                    )
                )
                mails.send_mail(
                    to_addr=email,
                    mail=mails.FORGOT_PASSWORD,
                    reset_link=reset_link
                )
                status.push_status_message(status_message, kind='success', trust=False)
            else:
                status.push_status_message('You have recently requested to change your password. Please wait a '
                                           'few minutes before trying again.', kind='error', trust=False)
        else:
            status.push_status_message(status_message, kind='success', trust=False)
    else:
        forms.push_errors_to_status(form.errors)
        # Don't go anywhere

    return {}
Exemplo n.º 19
0
    def test_has_public_projects_and_components(self):
        # I go to my own profile
        url = web_url_for('profile_view_id', uid=self.me._primary_key)
        # I see the title of both my project and component
        res = self.app.get(url, auth=self.me.auth)
        assert_in_html(self.component.title, res)
        assert_in_html(self.project.title, res)

        # Another user can also see my public project and component
        url = web_url_for('profile_view_id', uid=self.me._primary_key)
        # I see the title of both my project and component
        res = self.app.get(url, auth=self.user.auth)
        assert_in_html(self.component.title, res)
        assert_in_html(self.project.title, res)
Exemplo n.º 20
0
def figshare_oauth_callback(auth, **kwargs):

    user = auth.user

    nid = kwargs.get('nid') or kwargs.get('pid')
    node = models.Node.load(nid) if nid else None

    # Fail if node provided and user not contributor
    if node and not node.is_contributor(user):
        raise HTTPError(http.FORBIDDEN)

    if user is None:
        raise HTTPError(http.NOT_FOUND)
    if kwargs.get('nid') and not node:
        raise HTTPError(http.NOT_FOUND)

    figshare_user = user.get_addon('figshare')

    verifier = request.args.get('oauth_verifier')

    access_token, access_token_secret = oauth_get_token(
        figshare_user.oauth_request_token,
        figshare_user.oauth_request_token_secret,
        verifier
    )
    # Handle request cancellations from FigShare's API
    if not access_token or not access_token_secret:
        push_status_message('figshare authorization request cancelled.')
        if node:
            return redirect(node.web_url_for('node_setting'))
        return redirect(web_url_for('user_addons'))

    figshare_user.oauth_request_token = None
    figshare_user.oauth_request_token_secret = None
    figshare_user.oauth_access_token = access_token
    figshare_user.oauth_access_token_secret = access_token_secret
    figshare_user.save()

    if node:
        figshare_node = node.get_addon('figshare')

        figshare_node.user_settings = figshare_user
        figshare_node.save()

    if node:
        return redirect(os.path.join(node.url, 'settings'))

    return redirect(web_url_for('user_addons'))
Exemplo n.º 21
0
def forgot_password_post():
    """Attempt to send user password reset or return respective error.
    """
    form = ForgotPasswordForm(request.form, prefix='forgot_password')

    if form.validate():
        email = form.email.data
        user_obj = get_user(email=email)
        if user_obj:
            user_obj.verification_key = security.random_string(20)
            user_obj.save()
            reset_link = "http://{0}{1}".format(
                request.host,
                web_url_for(
                    'reset_password',
                    verification_key=user_obj.verification_key
                )
            )
            mails.send_mail(
                to_addr=email,
                mail=mails.FORGOT_PASSWORD,
                reset_link=reset_link
            )
        status.push_status_message(
            ('An email with instructions on how to reset the password '
             'for the account associated with {0} has been sent. If you '
             'do not receive an email and believe you should have please '
             'contact OSF Support.').format(email), 'success')

    forms.push_errors_to_status(form.errors)
    return auth_login(forgot_password_form=form)
Exemplo n.º 22
0
def dropbox_oauth_finish(auth, **kwargs):
    """View called when the Oauth flow is completed. Adds a new DropboxUserSettings
    record to the user and saves the user's access token and account info.
    """
    if not auth.logged_in:
        raise HTTPError(http.FORBIDDEN)
    user = auth.user

    node = Node.load(session.data.get('dropbox_auth_nid'))
    result = finish_auth(node)
    # If result is a redirect response, follow the redirect
    if isinstance(result, BaseResponse):
        return result
    # Make sure user has dropbox enabled
    user.add_addon('dropbox')
    user.save()
    user_settings = user.get_addon('dropbox')
    user_settings.owner = user
    user_settings.access_token = result.access_token
    user_settings.dropbox_id = result.dropbox_id
    client = get_client_from_user_settings(user_settings)
    user_settings.dropbox_info = client.account_info()
    user_settings.save()

    if node:
        del session.data['dropbox_auth_nid']
        # Automatically use newly-created auth
        if node.has_addon('dropbox'):
            node_addon = node.get_addon('dropbox')
            node_addon.set_user_auth(user_settings)
            node_addon.save()
        return redirect(node.web_url_for('node_setting'))
    return redirect(web_url_for('user_addons'))
Exemplo n.º 23
0
def auth_logout(redirect_url=None):
    """
    Log out, delete current session and remove OSF cookie.
    Redirect to CAS logout which clears sessions and cookies for CAS and Shibboleth (if any).
    Final landing page may vary.
    HTTP Method: GET

    :param redirect_url: url to redirect user after CAS logout, default is 'goodbye'
    :return:
    """

    # OSF tells CAS where it wants to be redirected back after successful logout.
    # However, CAS logout flow may not respect this url if user is authenticated through remote identity provider.
    redirect_url = redirect_url or request.args.get('redirect_url') or web_url_for('goodbye', _absolute=True)
    # OSF log out, remove current OSF session
    osf_logout()
    # set redirection to CAS log out (or log in if `reauth` is present)
    if 'reauth' in request.args:
        cas_endpoint = cas.get_login_url(redirect_url)
    else:
        cas_endpoint = cas.get_logout_url(redirect_url)
    resp = redirect(cas_endpoint)
    # delete OSF cookie
    resp.delete_cookie(settings.COOKIE_NAME, domain=settings.OSF_COOKIE_DOMAIN)

    return resp
Exemplo n.º 24
0
def get_settings_url(uid, user):
    if uid == user._id:
        return web_url_for('user_notifications', _absolute=True)

    node = website_models.Node.load(uid)
    assert node, 'get_settings_url recieved an invalid Node id'
    return node.web_url_for('node_setting', _guid=True, _absolute=True)
Exemplo n.º 25
0
def auth_email_logout(token, user):
    """
    When a user is adding an email or merging an account, add the email to the user and log them out.
    """

    redirect_url = cas.get_logout_url(service_url=cas.get_login_url(service_url=web_url_for('index', _absolute=True)))
    try:
        unconfirmed_email = user.get_unconfirmed_email_for_token(token)
    except InvalidTokenError:
        raise HTTPError(http.BAD_REQUEST, data={
            'message_short': 'Bad token',
            'message_long': 'The provided token is invalid.'
        })
    except ExpiredTokenError:
        status.push_status_message('The private link you used is expired.')
        raise HTTPError(http.BAD_REQUEST, data={
            'message_short': 'Expired link',
            'message_long': 'The private link you used is expired.'
        })
    try:
        user_merge = User.find_one(Q('emails', 'eq', unconfirmed_email))
    except NoResultsFound:
        user_merge = False
    if user_merge:
        remove_sessions_for_user(user_merge)
    user.email_verifications[token]['confirmed'] = True
    user.save()
    remove_sessions_for_user(user)
    resp = redirect(redirect_url)
    resp.delete_cookie(settings.COOKIE_NAME, domain=settings.OSF_COOKIE_DOMAIN)
    return resp
Exemplo n.º 26
0
def reset_password(auth, **kwargs):
    if auth.logged_in:
        return auth_logout(redirect_url=request.url)
    verification_key = kwargs['verification_key']
    form = ResetPasswordForm(request.form)

    user_obj = get_user(verification_key=verification_key)
    if not user_obj:
        error_data = {'message_short': 'Invalid url.',
            'message_long': 'The verification key in the URL is invalid or '
            'has expired.'}
        raise HTTPError(400, data=error_data)

    if request.method == 'POST' and form.validate():
        # new random verification key, allows CAS to authenticate the user w/o password one time only.
        user_obj.verification_key = security.random_string(20)
        user_obj.set_password(form.password.data)
        user_obj.save()
        status.push_status_message('Password reset', 'success')
        # Redirect to CAS and authenticate the user with a verification key.
        return redirect(cas.get_login_url(
            web_url_for('user_account', _absolute=True),
            auto=True,
            username=user_obj.username,
            verification_key=user_obj.verification_key
        ))

    forms.push_errors_to_status(form.errors)
    return {
        'verification_key': verification_key,
    }
Exemplo n.º 27
0
    def serialize_settings(self, node_settings, current_user, client=None):
        user_settings = node_settings.user_settings
        self.node_settings = node_settings
        current_user_settings = current_user.get_addon(self.addon_short_name)
        user_is_owner = user_settings is not None and user_settings.owner == current_user

        valid_credentials = self.credentials_are_valid(user_settings, client)

        result = {
            'userIsOwner': user_is_owner,
            'nodeHasAuth': node_settings.has_auth,
            'urls': self.serialized_urls,
            'validCredentials': valid_credentials,
            'userHasAuth': current_user_settings is not None and current_user_settings.has_auth,
        }

        if node_settings.has_auth:
            # Add owner's profile URL
            result['urls']['owner'] = web_url_for(
                'profile_view_id',
                uid=user_settings.owner._id
            )
            result['ownerName'] = user_settings.owner.fullname
            # Show available folders
            if node_settings.folder_id is None:
                result['folder'] = {'name': None, 'path': None}
            elif valid_credentials:
                result['folder'] = self.serialized_folder(node_settings)
        return result
Exemplo n.º 28
0
def googledrive_oauth_start(auth, **kwargs):
    """View function that does OAuth Authorization
    and returns access token"""
    # Run through the OAuth flow and retrieve credentials
    # Store the node ID on the session in order to get the correct redirect URL
    # upon finishing the flow
    user = auth.user
    nid = kwargs.get('nid') or kwargs.get('pid')
    node = models.Node.load(nid) if nid else None
    node_addon = user.get_addon('googledrive')

    # Fail if node provided and user not contributor
    if node and not node.is_contributor(user):
        raise HTTPError(http.FORBIDDEN)

    # Handle if user has already authorized google drive
    if node_addon and node_addon.has_auth:
        return redirect(web_url_for('user_addons'))

    client = GoogleAuthClient()
    authorization_url, state = client.start()

    session.data['googledrive_auth_state'] = state
    if nid:
        session.data['googledrive_auth_nid'] = nid

    return redirect(authorization_url)
Exemplo n.º 29
0
 def test_user_no_public_projects_or_components(self):
     # I go to other user's profile
     url = web_url_for('profile_view_id', uid=self.user._primary_key)
     # User has no public components/projects
     res = self.app.get(url, auth=self.me.auth)
     assert_in('This user has no public projects', res)
     assert_in('This user has no public components', res)
Exemplo n.º 30
0
def meeting_hook():
    """View function for email conference submission.
    """
    message = ConferenceMessage()

    try:
        message.verify()
    except ConferenceError as error:
        logger.error(error)
        raise HTTPError(httplib.NOT_ACCEPTABLE)

    try:
        conference = Conference.get_by_endpoint(message.conference_name, active=False)
    except ConferenceError as error:
        logger.error(error)
        raise HTTPError(httplib.NOT_ACCEPTABLE)

    if not conference.active:
        send_mail(
            message.sender_email,
            CONFERENCE_INACTIVE,
            fullname=message.sender_display,
            presentations_url=web_url_for('conference_view', _absolute=True),
            can_change_preferences=False,
            logo=settings.OSF_MEETINGS_LOGO,
        )
        raise HTTPError(httplib.NOT_ACCEPTABLE)

    add_poster_by_email(conference=conference, message=message)
Exemplo n.º 31
0
 def test_web_url_for(self):
     with self.app.test_request_context():
         assert web_url_for('dummy_view', pid='123') == '/123/'
Exemplo n.º 32
0
def add_poster_by_email(conference, message):
    """
    :param Conference conference:
    :param ConferenceMessage message:
    """
    # Fail if no attachments
    if not message.attachments:
        return send_mail(
            message.sender_email,
            CONFERENCE_FAILED,
            fullname=message.sender_display,
        )

    created = []

    with TokuTransaction():
        user, user_created = utils.get_or_create_user(
            message.sender_display,
            message.sender_email,
            message.is_spam,
        )
        if user_created:
            created.append(user)
            set_password_url = web_url_for(
                'reset_password',
                verification_key=user.verification_key,
                _absolute=True,
            )
        else:
            set_password_url = None

        node, node_created = utils.get_or_create_node(message.subject, user)
        if node_created:
            created.append(node)

        utils.provision_node(conference, message, node, user)
        utils.record_message(message, created)

    utils.upload_attachments(user, node, message.attachments)

    download_url = node.web_url_for(
        'addon_view_or_download_file',
        path=message.attachments[0].filename,
        provider='osfstorage',
        action='download',
        _absolute=True,
    )

    # Send confirmation email
    send_mail(
        message.sender_email,
        CONFERENCE_SUBMITTED,
        conf_full_name=conference.name,
        conf_view_url=web_url_for(
            'conference_results',
            meeting=message.conference_name,
            _absolute=True,
        ),
        fullname=message.sender_display,
        user_created=user_created,
        set_password_url=set_password_url,
        profile_url=user.absolute_url,
        node_url=node.absolute_url,
        file_url=download_url,
        presentation_type=message.conference_category.lower(),
        is_spam=message.is_spam,
    )
Exemplo n.º 33
0
def confirm_email_get(token, auth=None, **kwargs):
    """View for email confirmation links.
    Authenticates and redirects to user settings page if confirmation is
    successful, otherwise shows an "Expired Link" error.

    methods: GET
    """
    user = User.load(kwargs['uid'])
    is_merge = 'confirm_merge' in request.args
    is_initial_confirmation = not user.date_confirmed

    if user is None:
        raise HTTPError(http.NOT_FOUND)

    if auth and auth.user and (auth.user._id == user._id or auth.user._id == user.merged_by._id):
        if not is_merge:
            # determine if the user registered through a campaign
            campaign = campaigns.campaign_for_user(user)
            if campaign:
                return redirect(
                    campaigns.campaign_url_for(campaign)
                )
            if len(auth.user.emails) == 1 and len(auth.user.email_verifications) == 0:
                status.push_status_message(language.WELCOME_MESSAGE, 'default', jumbotron=True)

            if token in auth.user.email_verifications:
                status.push_status_message(language.CONFIRM_ALTERNATE_EMAIL_ERROR, 'danger')
            # Go to home page
            return redirect(web_url_for('index'))

        status.push_status_message(language.MERGE_COMPLETE, 'success')
        return redirect(web_url_for('user_account'))

    try:
        user.confirm_email(token, merge=is_merge)
    except exceptions.EmailConfirmTokenError as e:
        raise HTTPError(http.BAD_REQUEST, data={
            'message_short': e.message_short,
            'message_long': e.message_long
        })

    if is_initial_confirmation:
        user.date_last_login = datetime.datetime.utcnow()
        user.save()

        # Send out our welcome message
        mails.send_mail(
            to_addr=user.username,
            mail=mails.WELCOME,
            mimetype='html',
            user=user
        )

    # Redirect to CAS and authenticate the user with a verification key.
    user.verification_key = security.random_string(20)
    user.save()

    return redirect(cas.get_login_url(
        request.url,
        auto=True,
        username=user.username,
        verification_key=user.verification_key
    ))
Exemplo n.º 34
0
def confirm_email_get(token, auth=None, **kwargs):
    """
    View for email confirmation links. Authenticates and redirects to user settings page if confirmation is successful,
    otherwise shows an "Expired Link" error.
    HTTP Method: GET
    """

    is_merge = 'confirm_merge' in request.args

    try:
        if not is_merge or not check_select_for_update():
            user = OSFUser.objects.get(guids___id=kwargs['uid'])
        else:
            user = OSFUser.objects.filter(guids___id=kwargs['uid']).select_for_update().get()
    except OSFUser.DoesNotExist:
        raise HTTPError(http.NOT_FOUND)

    is_initial_confirmation = not user.date_confirmed
    log_out = request.args.get('logout', None)

    # if the user is merging or adding an email (they already are an osf user)
    if log_out:
        return auth_email_logout(token, user)

    if auth and auth.user and (auth.user._id == user._id or auth.user._id == user.merged_by._id):
        if not is_merge:
            # determine if the user registered through a campaign
            campaign = campaigns.campaign_for_user(user)
            if campaign:
                return redirect(campaigns.campaign_url_for(campaign))

            # go to home page with push notification
            if auth.user.emails.count() == 1 and len(auth.user.email_verifications) == 0:
                status.push_status_message(language.WELCOME_MESSAGE, kind='default', jumbotron=True, trust=True)
            if token in auth.user.email_verifications:
                status.push_status_message(language.CONFIRM_ALTERNATE_EMAIL_ERROR, kind='danger', trust=True)
            return redirect(web_url_for('index'))

        status.push_status_message(language.MERGE_COMPLETE, kind='success', trust=False)
        return redirect(web_url_for('user_account'))

    try:
        user.confirm_email(token, merge=is_merge)
    except exceptions.EmailConfirmTokenError as e:
        raise HTTPError(http.BAD_REQUEST, data={
            'message_short': e.message_short,
            'message_long': e.message_long
        })

    if is_initial_confirmation:
        user.update_date_last_login()
        user.save()

        # send out our welcome message
        mails.send_mail(
            to_addr=user.username,
            mail=mails.WELCOME,
            mimetype='html',
            user=user
        )

    # new random verification key, allows CAS to authenticate the user w/o password one-time only.
    user.verification_key = generate_verification_key()
    user.save()
    # redirect to CAS and authenticate the user with a verification key.
    return redirect(cas.get_login_url(
        request.url,
        username=user.username,
        verification_key=user.verification_key
    ))
Exemplo n.º 35
0
def external_login_confirm_email_get(auth, uid, token):
    """
    View for email confirmation links when user first login through external identity provider.
    HTTP Method: GET

    When users click the confirm link, they are expected not to be logged in. If not, they will be logged out first and
    redirected back to this view. After OSF verifies the link and performs all actions, they will be automatically
    logged in through CAS and redirected back to this view again being authenticated.

    :param auth: the auth context
    :param uid: the user's primary key
    :param token: the verification token
    """

    user = OSFUser.load(uid)
    if not user:
        raise HTTPError(http.BAD_REQUEST)

    destination = request.args.get('destination')
    if not destination:
        raise HTTPError(http.BAD_REQUEST)

    # if user is already logged in
    if auth and auth.user:
        # if it is a wrong user
        if auth.user._id != user._id:
            return auth_logout(redirect_url=request.url)
        # if it is the expected user
        new = request.args.get('new', None)
        if destination in campaigns.get_campaigns():
            # external domain takes priority
            campaign_url = campaigns.external_campaign_url_for(destination)
            if not campaign_url:
                campaign_url = campaigns.campaign_url_for(destination)
            return redirect(campaign_url)
        if new:
            status.push_status_message(language.WELCOME_MESSAGE, kind='default', jumbotron=True, trust=True)
        return redirect(web_url_for('dashboard'))

    # token is invalid
    if token not in user.email_verifications:
        raise HTTPError(http.BAD_REQUEST)
    verification = user.email_verifications[token]
    email = verification['email']
    provider = verification['external_identity'].keys()[0]
    provider_id = verification['external_identity'][provider].keys()[0]
    # wrong provider
    if provider not in user.external_identity:
        raise HTTPError(http.BAD_REQUEST)
    external_status = user.external_identity[provider][provider_id]

    try:
        ensure_external_identity_uniqueness(provider, provider_id, user)
    except ValidationError as e:
        raise HTTPError(http.FORBIDDEN, e.message)

    if not user.is_registered:
        user.register(email)

    if not user.emails.filter(address=email.lower()):
        user.emails.create(address=email.lower())

    user.date_last_logged_in = timezone.now()
    user.external_identity[provider][provider_id] = 'VERIFIED'
    user.social[provider.lower()] = provider_id
    del user.email_verifications[token]
    user.verification_key = generate_verification_key()
    user.save()

    service_url = request.url

    if external_status == 'CREATE':
        mails.send_mail(
            to_addr=user.username,
            mail=mails.WELCOME,
            mimetype='html',
            user=user
        )
        service_url += '&{}'.format(urllib.urlencode({'new': 'true'}))
    elif external_status == 'LINK':
        mails.send_mail(
            user=user,
            to_addr=user.username,
            mail=mails.EXTERNAL_LOGIN_LINK_SUCCESS,
            external_id_provider=provider,
        )

    # redirect to CAS and authenticate the user with the verification key
    return redirect(cas.get_login_url(
        service_url,
        username=user.username,
        verification_key=user.verification_key
    ))
Exemplo n.º 36
0
def add_poster_by_email(conference, message):
    """
    :param Conference conference:
    :param ConferenceMessage message:
    """
    # Fail if no attachments
    if not message.attachments:
        return send_mail(
            message.sender_email,
            CONFERENCE_FAILED,
            fullname=message.sender_display,
        )

    created = []

    with TokuTransaction():
        user, user_created = get_or_create_user(
            message.sender_display,
            message.sender_email,
            message.is_spam,
        )
        if user_created:
            created.append(user)
            user.system_tags.append('osf4m')
            set_password_url = web_url_for(
                'reset_password',
                verification_key=user.verification_key,
                _absolute=True,
            )
            user.date_last_login = datetime.utcnow()
            user.save()
        else:
            set_password_url = None

        node, node_created = utils.get_or_create_node(message.subject, user)
        if node_created:
            created.append(node)
            node.system_tags.append('osf4m')
            node.save()

        utils.provision_node(conference, message, node, user)
        utils.record_message(message, created)
    # Prevent circular import error
    from framework.auth import signals as auth_signals
    if user_created:
        auth_signals.user_confirmed.send(user)

    utils.upload_attachments(user, node, message.attachments)

    download_url = node.web_url_for(
        'addon_view_or_download_file',
        path=message.attachments[0].filename,
        provider='osfstorage',
        action='download',
        _absolute=True,
    )

    # Send confirmation email
    send_mail(
        message.sender_email,
        CONFERENCE_SUBMITTED,
        conf_full_name=conference.name,
        conf_view_url=web_url_for(
            'conference_results',
            meeting=message.conference_name,
            _absolute=True,
        ),
        fullname=message.sender_display,
        user_created=user_created,
        set_password_url=set_password_url,
        profile_url=user.absolute_url,
        node_url=node.absolute_url,
        file_url=download_url,
        presentation_type=message.conference_category.lower(),
        is_spam=message.is_spam,
    )
    if node_created and user_created:
        signals.osf4m_user_created.send(user, conference=conference, node=node)
Exemplo n.º 37
0
def add_poster_by_email(conference, message):
    """
    :param Conference conference:
    :param ConferenceMessage message:
    """
    # Fail if no attachments
    if not message.attachments:
        return send_mail(
            message.sender_email,
            CONFERENCE_FAILED,
            fullname=message.sender_display,
        )

    nodes_created = []
    users_created = []

    with transaction.atomic():
        user, user_created = get_or_create_user(
            message.sender_display,
            message.sender_email,
            is_spam=message.is_spam,
        )
        if user_created:
            user.save(
            )  # need to save in order to access m2m fields (e.g. tags)
            users_created.append(user)
            user.add_system_tag('osf4m')
            user.update_date_last_login()
            user.save()

            # must save the user first before accessing user._id
            set_password_url = web_url_for(
                'reset_password_get',
                uid=user._id,
                token=user.verification_key_v2['token'],
                _absolute=True,
            )
        else:
            set_password_url = None

        node, node_created = Node.objects.get_or_create(
            title__iexact=message.subject,
            is_deleted=False,
            _contributors__guids___id=user._id,
            defaults={
                'title': message.subject,
                'creator': user
            })
        if node_created:
            nodes_created.append(node)
            node.add_system_tag('osf4m')
            node.save()

        utils.provision_node(conference, message, node, user)
        utils.record_message(message, nodes_created, users_created)
    # Prevent circular import error
    from framework.auth import signals as auth_signals
    if user_created:
        auth_signals.user_confirmed.send(user)

    utils.upload_attachments(user, node, message.attachments)

    download_url = node.web_url_for(
        'addon_view_or_download_file',
        path=message.attachments[0].filename,
        provider='osfstorage',
        action='download',
        _absolute=True,
    )

    # Send confirmation email
    send_mail(
        message.sender_email,
        CONFERENCE_SUBMITTED,
        conf_full_name=conference.name,
        conf_view_url=web_url_for(
            'conference_results',
            meeting=message.conference_name,
            _absolute=True,
        ),
        fullname=message.sender_display,
        user_created=user_created,
        set_password_url=set_password_url,
        profile_url=user.absolute_url,
        node_url=node.absolute_url,
        file_url=download_url,
        presentation_type=message.conference_category.lower(),
        is_spam=message.is_spam,
    )
    if node_created and user_created:
        signals.osf4m_user_created.send(user, conference=conference, node=node)
Exemplo n.º 38
0
def conference_submissions_sql(conf):
    """
    Serializes all meeting submissions to a conference (returns array of dictionaries)

    :param obj conf: Conference object.

    """
    submission1_name = conf.field_names['submission1']
    submission2_name = conf.field_names['submission2']
    conference_url = web_url_for('conference_results', meeting=conf.endpoint)
    abstract_node_content_type_id = ContentType.objects.get_for_model(
        AbstractNode).id
    osf_user_content_type_id = ContentType.objects.get_for_model(OSFUser).id

    with connection.cursor() as cursor:
        cursor.execute(
            """
            SELECT  json_build_object(
                    'id', ROW_NUMBER() OVER (ORDER BY 1),
                    'title', osf_abstractnode.title,
                    'nodeUrl', '/' || GUID._id || '/',
                    'author', CASE WHEN AUTHOR.family_name != '' THEN AUTHOR.family_name ELSE AUTHOR.fullname END,
                    'authorUrl', '/' || AUTHOR_GUID._id || '/',
                    'category', CASE WHEN %s = ANY(TAGS_LIST.tag_list) THEN %s ELSE %s END,
                    'download', COALESCE(DOWNLOAD_COUNT, 0),
                    'downloadUrl', COALESCE('/project/' || GUID._id || '/files/osfstorage/' || FILE._id || '/?action=download', ''),
                    'dateCreated', osf_abstractnode.created,
                    'confName', %s,
                    'confUrl', %s,
                    'tags', array_to_string(TAGS_LIST.tag_list, ' ')
                )
            FROM osf_abstractnode
              INNER JOIN osf_abstractnode_tags ON (osf_abstractnode.id = osf_abstractnode_tags.abstractnode_id)
              LEFT JOIN LATERAL(
                SELECT array_agg(osf_tag.name) AS tag_list
                  FROM osf_tag
                  INNER JOIN osf_abstractnode_tags ON (osf_tag.id = osf_abstractnode_tags.tag_id)
                  WHERE (osf_tag.system = FALSE AND osf_abstractnode_tags.abstractnode_id = osf_abstractnode.id)
                ) AS TAGS_LIST ON TRUE -- Concatenates tag names with space in between
              LEFT JOIN LATERAL (
                        SELECT osf_osfuser.*
                        FROM osf_osfuser
                          INNER JOIN osf_contributor ON (osf_contributor.user_id = osf_osfuser.id)
                        WHERE (osf_contributor.node_id = osf_abstractnode.id AND osf_contributor.visible = TRUE)
                        ORDER BY osf_contributor._order ASC
                        LIMIT 1
                        ) AUTHOR ON TRUE  -- Returns first visible contributor
              LEFT JOIN LATERAL (
                SELECT osf_guid._id
                FROM osf_guid
                WHERE (osf_guid.object_id = osf_abstractnode.id AND osf_guid.content_type_id = %s) -- Content type for AbstractNode
                ORDER BY osf_guid.created DESC
                LIMIT 1   -- Returns node guid
              ) GUID ON TRUE
              LEFT JOIN LATERAL (
                SELECT osf_guid._id
                FROM osf_guid
                WHERE (osf_guid.object_id = AUTHOR.id AND osf_guid.content_type_id = %s)  -- Content type for OSFUser
                LIMIT 1
              ) AUTHOR_GUID ON TRUE   -- Returns author_guid
              LEFT JOIN LATERAL (
                SELECT osf_basefilenode.*
                FROM osf_basefilenode
                WHERE (
                  osf_basefilenode.type = 'osf.osfstoragefile'
                  AND osf_basefilenode.provider = 'osfstorage'
                  AND osf_basefilenode.node_id = osf_abstractnode.id
                )
                LIMIT 1   -- Joins file
              ) FILE ON TRUE
              LEFT JOIN LATERAL (
                SELECT P.total AS DOWNLOAD_COUNT
                FROM osf_pagecounter AS P
                WHERE P._id = 'download:' || GUID._id || ':' || FILE._id
                LIMIT 1
              ) DOWNLOAD_COUNT ON TRUE
            -- Get all the nodes for a specific meeting
            WHERE (osf_abstractnode_tags.tag_id IN
                   (SELECT U0.id AS Col1
                    FROM osf_tag U0
                    WHERE (U0.system = FALSE
                           AND UPPER(U0.name :: TEXT) = UPPER(%s)
                           AND U0.system = FALSE))
                   AND osf_abstractnode.is_deleted = FALSE
                   AND osf_abstractnode.is_public = TRUE
                   AND AUTHOR_GUID IS NOT NULL)
            ORDER BY osf_abstractnode.created DESC;

            """, [
                submission1_name, submission1_name, submission2_name,
                conf.name, conference_url, abstract_node_content_type_id,
                osf_user_content_type_id, conf.endpoint
            ])
        rows = cursor.fetchall()
        return [row[0] for row in rows]
Exemplo n.º 39
0
def forgot_password_get(auth, *args, **kwargs):
    """Return forgot password page upon.
    """
    if auth.logged_in:
        return redirect(web_url_for('dashboard'))
    return {}
Exemplo n.º 40
0
    def test_conference_results(self):
        conference = ConferenceFactory()

        url = web_url_for('conference_results', meeting=conference.endpoint)
        res = self.app.get(url)
        assert_equal(res.status_code, 200)
Exemplo n.º 41
0
def auth_login(auth, **kwargs):
    """
    This view serves as the entry point for OSF login and campaign login.
    HTTP Method: GET

        GET '/login/' without any query parameter:
            redirect to CAS login page with dashboard as target service

        GET '/login/?logout=true
            log user out and redirect to CAS login page with redirect_url or next_url as target service

        GET '/login/?campaign=instituion:
            if user is logged in, redirect to 'dashboard'
            show institution login

        GET '/login/?campaign=prereg:
            if user is logged in, redirect to prereg home page
            else show sign up page and notify user to sign in, set next to prereg home page

        GET '/login/?next=next_url:
            if user is logged in, redirect to next_url
            else redirect to CAS login page with next_url as target service
    """

    campaign = request.args.get('campaign')
    next_url = request.args.get('next')
    log_out = request.args.get('logout')
    must_login_warning = True

    if not campaign and not next_url and not log_out:
        if auth.logged_in:
            return redirect(web_url_for('dashboard'))
        return redirect(
            cas.get_login_url(web_url_for('dashboard', _absolute=True)))

    if campaign:
        must_login_warning = False
        next_url = campaigns.campaign_url_for(campaign)

    if not next_url:
        next_url = request.args.get('redirect_url')
        must_login_warning = False

    if not validate_next_url(next_url):
        raise HTTPError(http.BAD_REQUEST)

    if auth.logged_in:
        if not log_out:
            if next_url:
                return redirect(next_url)
            return redirect('dashboard')
        # redirect user to CAS for logout, return here w/o authentication
        return auth_logout(redirect_url=request.url)

    status_message = request.args.get('status', '')
    if status_message == 'expired':
        status.push_status_message('The private link you used is expired.',
                                   trust=False)
        status.push_status_message(
            'The private link you used is expired.  Please <a href="/settings/account/">'
            'resend email.</a>',
            trust=False)

    if next_url and must_login_warning:
        status.push_status_message(language.MUST_LOGIN, trust=False)

    # set login_url to form action, upon successful authentication specifically w/o logout=True,
    # allows for next to be followed or a redirect to the dashboard.
    redirect_url = web_url_for('auth_login', next=next_url, _absolute=True)

    data = {}
    if campaign and campaign in campaigns.CAMPAIGNS:
        if (campaign == 'institution'
                and settings.ENABLE_INSTITUTIONS) or campaign != 'institution':
            data['campaign'] = campaign
    data['login_url'] = cas.get_login_url(redirect_url)
    data['institution_redirect'] = cas.get_institution_target(redirect_url)
    data['redirect_url'] = next_url
    data['sign_up'] = request.args.get('sign_up', False)
    data['existing_user'] = request.args.get('existing_user', None)

    return data, http.OK
Exemplo n.º 42
0
    def auth_callback(self, user):
        """Exchange temporary credentials for permanent credentials

        This is called in the view that handles the user once they are returned
        to the OSF after authenticating on the external service.
        """

        # make sure the user has temporary credentials for this provider
        try:
            cached_credentials = session.data['oauth_states'][self.short_name]
        except KeyError:
            raise PermissionsError("OAuth flow not recognized.")

        if self._oauth_version == OAUTH1:
            request_token = request.args.get('oauth_token')

            # make sure this is the same user that started the flow
            if cached_credentials.get('token') != request_token:
                raise PermissionsError("Request token does not match")

            response = OAuth1Session(
                client_key=self.client_id,
                client_secret=self.client_secret,
                resource_owner_key=cached_credentials.get('token'),
                resource_owner_secret=cached_credentials.get('secret'),
                verifier=request.args.get('oauth_verifier'),
            ).fetch_access_token(self.callback_url)

        elif self._oauth_version == OAUTH2:
            state = request.args.get('state')

            # make sure this is the same user that started the flow
            if cached_credentials.get('state') != state:
                raise PermissionsError("Request token does not match")

            try:
                response = OAuth2Session(
                    self.client_id,
                    redirect_uri=web_url_for(
                        'oauth_callback',
                        service_name=self.short_name,
                        _absolute=True
                    ),
                ).fetch_token(
                    self.callback_url,
                    client_secret=self.client_secret,
                    code=request.args.get('code'),
                )
            except (MissingTokenError, RequestsHTTPError):
                raise HTTPError(http.SERVICE_UNAVAILABLE)

        # pre-set as many values as possible for the ``ExternalAccount``
        info = self._default_handle_callback(response)
        # call the hook for subclasses to parse values from the response
        info.update(self.handle_callback(response))

        try:
            # create a new ``ExternalAccount`` ...
            self.account = ExternalAccount(
                provider=self.short_name,
                provider_id=info['provider_id'],
                provider_name=self.name,
            )
            self.account.save()
        except KeyExistsException:
            # ... or get the old one
            self.account = ExternalAccount.find_one(
                Q('provider', 'eq', self.short_name) &
                Q('provider_id', 'eq', info['provider_id'])
            )
            assert self.account is not None

        # ensure that provider_name is correct
        self.account.provider_name = self.name
        # required
        self.account.oauth_key = info['key']

        # only for OAuth1
        self.account.oauth_secret = info.get('secret')

        # only for OAuth2
        self.account.expires_at = info.get('expires_at')
        self.account.refresh_token = info.get('refresh_token')

        # additional information
        self.account.display_name = info.get('display_name')
        self.account.profile_url = info.get('profile_url')

        self.account.save()

        # add it to the user's list of ``ExternalAccounts``
        if self.account not in user.external_accounts:
            user.external_accounts.append(self.account)
            user.save()
Exemplo n.º 43
0
def claim_user_form(auth, **kwargs):
    """
    View for rendering the set password page for a claimed user.
    Must have ``token`` as a querystring argument.
    Renders the set password form, validates it, and sets the user's password.
    HTTP Method: GET, POST
    """

    uid, pid = kwargs['uid'], kwargs['pid']
    token = request.form.get('token') or request.args.get('token')
    user = OSFUser.load(uid)

    # If unregistered user is not in database, or url bears an invalid token raise HTTP 400 error
    if not user or not verify_claim_token(user, token, pid):
        error_data = {
            'message_short':
            'Invalid url.',
            'message_long':
            'Claim user does not exists, the token in the URL is invalid or has expired.'
        }
        raise HTTPError(http_status.HTTP_400_BAD_REQUEST, data=error_data)

    # If user is logged in, redirect to 're-enter password' page
    if auth.logged_in:
        return redirect(
            web_url_for('claim_user_registered', uid=uid, pid=pid,
                        token=token))

    unclaimed_record = user.unclaimed_records[pid]
    user.fullname = unclaimed_record['name']
    user.update_guessed_names()
    # The email can be the original referrer email if no claimer email has been specified.
    claimer_email = unclaimed_record.get(
        'claimer_email') or unclaimed_record.get('email')
    # If there is a registered user with this email, redirect to 're-enter password' page
    try:
        user_from_email = OSFUser.objects.get(
            emails__address=claimer_email.lower().strip(
            )) if claimer_email else None
    except OSFUser.DoesNotExist:
        user_from_email = None
    if user_from_email and user_from_email.is_registered:
        return redirect(
            web_url_for('claim_user_registered', uid=uid, pid=pid,
                        token=token))

    form = SetEmailAndPasswordForm(request.form, token=token)
    if request.method == 'POST':
        if not form.validate():
            forms.push_errors_to_status(form.errors)
        elif settings.RECAPTCHA_SITE_KEY and not validate_recaptcha(
                request.form.get('g-recaptcha-response'),
                remote_ip=request.remote_addr):
            status.push_status_message('Invalid captcha supplied.',
                                       kind='error')
        else:
            username, password = claimer_email, form.password.data
            if not username:
                raise HTTPError(
                    http_status.HTTP_400_BAD_REQUEST,
                    data=dict(
                        message_long=
                        'No email associated with this account. Please claim this '
                        'account on the project to which you were invited.'))

            user.register(
                username=username,
                password=password,
                accepted_terms_of_service=form.accepted_terms_of_service.data)
            # Clear unclaimed records
            user.unclaimed_records = {}
            user.verification_key = generate_verification_key()
            user.save()
            # Authenticate user and redirect to project page
            status.push_status_message(language.CLAIMED_CONTRIBUTOR,
                                       kind='success',
                                       trust=True)
            # Redirect to CAS and authenticate the user with a verification key.
            provider = PreprintProvider.load(pid)
            redirect_url = None
            if provider:
                redirect_url = web_url_for('auth_login',
                                           next=provider.landing_url,
                                           _absolute=True)
            else:
                # Add related claimed tags to user
                _add_related_claimed_tag_to_user(pid, user)
                redirect_url = web_url_for('resolve_guid',
                                           guid=pid,
                                           _absolute=True)

            return redirect(
                cas.get_login_url(redirect_url,
                                  username=user.username,
                                  verification_key=user.verification_key))

    return {
        'firstname': user.given_name,
        'email': claimer_email if claimer_email else '',
        'fullname': user.fullname,
        'form': forms.utils.jsonify(form) if is_json_request() else form,
        'osf_contact_email': settings.OSF_CONTACT_EMAIL,
    }
Exemplo n.º 44
0
 def test_cant_see_profile_if_not_logged_in(self):
     url = web_url_for('profile_view')
     res = self.app.get(url)
     res = res.follow()
     assert_equal(res.status_code, 301)
     assert_in('/login/', res.headers['Location'])
Exemplo n.º 45
0
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_status.HTTP_400_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_status.HTTP_400_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 = OSFUser.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'],
        _absolute=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'],
        can_change_preferences=False,
        osf_contact_email=settings.OSF_CONTACT_EMAIL,
    )
    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,
        can_change_preferences=False,
        osf_contact_email=settings.OSF_CONTACT_EMAIL,
    )
Exemplo n.º 46
0
 def setUp(self):
     super(TestResendConfirmation, self).setUp()
     self.unconfirmed_user = UnconfirmedUserFactory()
     self.confirmed_user = UserFactory()
     self.get_url = web_url_for('resend_confirmation_get')
     self.post_url = web_url_for('resend_confirmation_post')
Exemplo n.º 47
0
def goodbye():
    # Redirect to dashboard if logged in
    if _get_current_user():
        return redirect(util.web_url_for('dashboard'))
    status.push_status_message(language.LOGOUT, 'info')
    return {}
Exemplo n.º 48
0
 def test_web_url_for_with_multiple_urls(self):
     with self.app.test_request_context():
         url = web_url_for('dummy_view', pid='123', nid='abc')
         assert url == '/123/component/abc/'
Exemplo n.º 49
0
 def test_authenticate_two_factor_returns_correct_response(self):
     response = authenticate_two_factor(self.user)
     assert_true(isinstance(response, BaseResponse))
     assert_equal(response.location, web_url_for('two_factor'))
     assert_equal(response.status_code, 302)
Exemplo n.º 50
0
Arquivo: cas.py Projeto: jwalz/osf.io
def make_response_from_ticket(ticket, service_url):
    """
    Given a CAS ticket and service URL, attempt to validate the user and return a proper redirect response.

    :param str ticket: CAS service ticket
    :param str service_url: Service URL from which the authentication request originates
    :return: redirect response
    """

    service_furl = furl.furl(service_url)
    # `service_url` is guaranteed to be removed of `ticket` parameter, which has been pulled off in
    # `framework.sessions.before_request()`.
    if 'ticket' in service_furl.args:
        service_furl.args.pop('ticket')
    client = get_client()
    cas_resp = client.service_validate(ticket, service_furl.url)
    if cas_resp.authenticated:
        user, external_credential, action = get_user_from_cas_resp(cas_resp)
        user_updates = {}  # serialize updates to user to be applied async
        # user found and authenticated
        if user and action == 'authenticate':
            print_cas_log(
                f'CAS response - authenticating user: user=[{user._id}], '
                f'external=[{external_credential}], action=[{action}]',
                LogLevel.INFO,
            )
            # If users check the TOS consent checkbox via CAS, CAS sets the attribute `termsOfServiceChecked` to `true`
            # and then release it to OSF among other authentication attributes. When OSF receives it, it trusts CAS and
            # updates the user object if this is THE FINAL STEP of the login flow. DON'T update TOS consent status when
            # `external_credential == true` (i.e. w/ `action == 'authenticate'` or `action == 'external_first_login'`)
            # since neither is the final step of a login flow.
            tos_checked_via_cas = cas_resp.attributes.get(
                'termsOfServiceChecked', 'false') == 'true'
            if tos_checked_via_cas:
                user_updates['accepted_terms_of_service'] = timezone.now()
                print_cas_log(
                    f'CAS TOS consent checked: {user.guids.first()._id}, {user.username}',
                    LogLevel.INFO)
            # if we successfully authenticate and a verification key is present, invalidate it
            if user.verification_key:
                user_updates['verification_key'] = None

            # if user is authenticated by external IDP, ask CAS to authenticate user for a second time
            # this extra step will guarantee that 2FA are enforced
            # current CAS session created by external login must be cleared first before authentication
            if external_credential:
                user.verification_key = generate_verification_key()
                user.save()
                print_cas_log(
                    f'CAS response - redirect existing external IdP login to verification key login: user=[{user._id}]',
                    LogLevel.INFO)
                return redirect(
                    get_logout_url(
                        get_login_url(service_url,
                                      username=user.username,
                                      verification_key=user.verification_key)))

            # if user is authenticated by CAS
            # TODO [CAS-27]: Remove Access Token From Service Validation
            print_cas_log(
                f'CAS response - finalizing authentication: user=[{user._id}]',
                LogLevel.INFO)
            return authenticate(user,
                                cas_resp.attributes.get('accessToken', ''),
                                redirect(service_furl.url), user_updates)
        # first time login from external identity provider
        if not user and external_credential and action == 'external_first_login':
            print_cas_log(
                f'CAS response - first login from external IdP: '
                f'external=[{external_credential}], action=[{action}]',
                LogLevel.INFO,
            )
            from website.util import web_url_for
            # orcid attributes can be marked private and not shared, default to orcid otherwise
            fullname = u'{} {}'.format(
                cas_resp.attributes.get('given-names', ''),
                cas_resp.attributes.get('family-name', '')).strip()
            # TODO [CAS-27]: Remove Access Token From Service Validation
            user = {
                'external_id_provider': external_credential['provider'],
                'external_id': external_credential['id'],
                'fullname': fullname,
                'access_token': cas_resp.attributes.get('accessToken', ''),
                'service_url': service_furl.url,
            }
            print_cas_log(
                f'CAS response - creating anonymous session: external=[{external_credential}]',
                LogLevel.INFO)
            return external_first_login_authenticate(
                user, redirect(web_url_for('external_login_email_get')))
    # Unauthorized: ticket could not be validated, or user does not exist.
    print_cas_log(
        'Ticket validation failed or user does not exist. Redirect back to service URL (logged out).',
        LogLevel.ERROR)
    return redirect(service_furl.url)
Exemplo n.º 51
0
    def auth_callback(self, user, **kwargs):
        """Exchange temporary credentials for permanent credentials

        This is called in the view that handles the user once they are returned
        to the OSF after authenticating on the external service.
        """

        if 'error' in request.args:
            return False

        # make sure the user has temporary credentials for this provider
        try:
            cached_credentials = session.data['oauth_states'][self.short_name]
        except KeyError:
            raise PermissionsError('OAuth flow not recognized.')

        if self._oauth_version == OAUTH1:
            request_token = request.args.get('oauth_token')

            # make sure this is the same user that started the flow
            if cached_credentials.get('token') != request_token:
                raise PermissionsError('Request token does not match')

            response = OAuth1Session(
                client_key=self.client_id,
                client_secret=self.client_secret,
                resource_owner_key=cached_credentials.get('token'),
                resource_owner_secret=cached_credentials.get('secret'),
                verifier=request.args.get('oauth_verifier'),
            ).fetch_access_token(self.callback_url)

        elif self._oauth_version == OAUTH2:
            state = request.args.get('state')

            # make sure this is the same user that started the flow
            if cached_credentials.get('state') != state:
                raise PermissionsError('Request token does not match')

            try:
                # Quirk: Similarly to the `oauth2/authorize` endpoint, the `oauth2/access_token`
                #        endpoint of Bitbucket would fail if a not-none or non-empty `redirect_uri`
                #        were provided in the body of the POST request.
                if self.short_name in ADDONS_OAUTH_NO_REDIRECT:
                    redirect_uri = None
                else:
                    redirect_uri = web_url_for('oauth_callback',
                                               service_name=self.short_name,
                                               _absolute=True)
                response = OAuth2Session(
                    self.client_id,
                    redirect_uri=redirect_uri,
                ).fetch_token(
                    self.callback_url,
                    client_secret=self.client_secret,
                    code=request.args.get('code'),
                )
            except (MissingTokenError, RequestsHTTPError):
                raise HTTPError(http_status.HTTP_503_SERVICE_UNAVAILABLE)
        # pre-set as many values as possible for the ``ExternalAccount``
        info = self._default_handle_callback(response)
        # call the hook for subclasses to parse values from the response
        info.update(self.handle_callback(response))

        return self._set_external_account(user, info)
Exemplo n.º 52
0
def make_response_from_ticket(ticket, service_url):
    """
    Given a CAS ticket and service URL, attempt to validate the user and return a proper redirect response.

    :param str ticket: CAS service ticket
    :param str service_url: Service URL from which the authentication request originates
    :return: redirect response
    """

    service_furl = furl.furl(service_url)
    # `service_url` is guaranteed to be removed of `ticket` parameter, which has been pulled off in
    # `framework.sessions.before_request()`.
    if 'ticket' in service_furl.args:
        service_furl.args.pop('ticket')
    client = get_client()
    cas_resp = client.service_validate(ticket, service_furl.url)
    if cas_resp.authenticated:
        user, external_credential, action = get_user_from_cas_resp(cas_resp)
        # user found and authenticated
        if user and action == 'authenticate':
            # if we successfully authenticate and a verification key is present, invalidate it
            if user.verification_key:
                user.verification_key = None
                user.save()

            # if user is authenticated by external IDP, ask CAS to authenticate user for a second time
            # this extra step will guarantee that 2FA are enforced
            # current CAS session created by external login must be cleared first before authentication
            if external_credential:
                user.verification_key = generate_verification_key()
                user.save()
                return redirect(
                    get_logout_url(
                        get_login_url(service_url,
                                      username=user.username,
                                      verification_key=user.verification_key)))

            # if user is authenticated by CAS
            # TODO [CAS-27]: Remove Access Token From Service Validation
            return authenticate(user,
                                cas_resp.attributes.get('accessToken', ''),
                                redirect(service_furl.url))
        # first time login from external identity provider
        if not user and external_credential and action == 'external_first_login':
            from website.util import web_url_for
            # orcid attributes can be marked private and not shared, default to orcid otherwise
            fullname = u'{} {}'.format(
                cas_resp.attributes.get('given-names', ''),
                cas_resp.attributes.get('family-name', '')).strip()
            if not fullname:
                fullname = external_credential['id']
            # TODO [CAS-27]: Remove Access Token From Service Validation
            user = {
                'external_id_provider': external_credential['provider'],
                'external_id': external_credential['id'],
                'fullname': fullname,
                'access_token': cas_resp.attributes.get('accessToken', ''),
                'service_url': service_furl.url,
            }
            return external_first_login_authenticate(
                user, redirect(web_url_for('external_login_email_get')))
    # Unauthorized: ticket could not be validated, or user does not exist.
    return redirect(service_furl.url)
Exemplo n.º 53
0
 def test_confererence_results_endpoint_is_case_insensitive(self):
     ConferenceFactory(endpoint='StudySwap')
     url = web_url_for('conference_results', meeting='studyswap')
     res = self.app.get(url)
     assert_equal(res.status_code, 200)
Exemplo n.º 54
0
 def test_basic_auth_returns_403(self):
     url = web_url_for('dashboard')
     ret = self.app.get(url, auth=('test', 'test'), expect_errors=True)
     assert_equal(ret.status_code, 403)
Exemplo n.º 55
0
 def serialized_urls(self):
     ret = self.addon_serialized_urls
     ret.update({'settings': web_url_for('user_addons')})
     return ret
def claim_user_registered(auth, node, **kwargs):
    """
    View that prompts user to enter their password in order to claim being a contributor on a project.
    A user must be logged in.
    """

    current_user = auth.user

    sign_out_url = web_url_for('auth_login', logout=True, next=request.url)
    if not current_user:
        return redirect(sign_out_url)

    # Logged in user should not be a contributor the project
    if node.is_contributor(current_user):
        logout_url = web_url_for('auth_logout', redirect_url=request.url)
        data = {
            'message_short': 'Already a contributor',
            'message_long': ('The logged-in user is already a contributor to this '
                'project. Would you like to <a href="{}">log out</a>?').format(logout_url)
        }
        raise HTTPError(http.BAD_REQUEST, data=data)

    uid, pid, token = kwargs['uid'], kwargs['pid'], kwargs['token']
    unreg_user = User.load(uid)
    if not verify_claim_token(unreg_user, token, pid=node._primary_key):
        error_data = {
            'message_short': 'Invalid url.',
            'message_long': 'The token in the URL is invalid or has expired.'
        }
        raise HTTPError(http.BAD_REQUEST, data=error_data)

    # Store the unreg_user data on the session in case the user registers
    # a new account
    session.data['unreg_user'] = {
        'uid': uid, 'pid': pid, 'token': token
    }

    form = PasswordForm(request.form)
    if request.method == 'POST':
        if form.validate():
            if current_user.check_password(form.password.data):
                node.replace_contributor(old=unreg_user, new=current_user)
                node.save()
                status.push_status_message(
                    'You are now a contributor to this project.',
                    kind='success',
                    trust=False
                )
                return redirect(node.url)
            else:
                status.push_status_message(language.LOGIN_FAILED, kind='warning', trust=False)
        else:
            forms.push_errors_to_status(form.errors)
    if is_json_request():
        form_ret = forms.utils.jsonify(form)
        user_ret = profile_utils.serialize_user(current_user, full=False)
    else:
        form_ret = form
        user_ret = current_user
    return {
        'form': form_ret,
        'user': user_ret,
        'signOutUrl': sign_out_url
    }
Exemplo n.º 57
0
 def setUp(self):
     super(TestCampaignsCASInstitutionLogin, self).setUp()
     self.url_login = web_url_for('auth_login', campaign='institution')
     self.url_register = web_url_for('auth_register',
                                     campaign='institution')
     self.service_url = web_url_for('dashboard', _absolute=True)
Exemplo n.º 58
0
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_status.HTTP_400_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_status.HTTP_400_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)
            osf4m_source_tag, created = Tag.all_tags.get_or_create(
                name=CampaignSourceTags.Osf4m.value, system=True)
            osf4m_claimed_tag, created = Tag.all_tags.get_or_create(
                name=CampaignClaimedTags.Osf4m.value, system=True)
            if user_obj.all_tags.filter(id=osf4m_source_tag.id,
                                        system=True).exists():
                user_obj.add_system_tag(osf4m_claimed_tag)
            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'],
    }
Exemplo n.º 59
0
def login_and_register_handler(auth, login=True, campaign=None, next_url=None, logout=None):
    """
    Non-view helper to handle `login` and `register` requests.

    :param auth: the auth context
    :param login: `True` if `GET /login`, `False` if `GET /register`
    :param campaign: a target campaign defined in `auth.campaigns`
    :param next_url: the service url for CAS login or redirect url for OSF
    :param logout: used only for `claim_user_registered`
    :return: data object that contains actions for `auth_register` and `auth_login`
    :raises: http.BAD_REQUEST
    """

    # Only allow redirects which are relative root or full domain. Disallows external redirects.
    if next_url and not validate_next_url(next_url):
        raise HTTPError(http.BAD_REQUEST)

    data = {
        'status_code': http.FOUND if login else http.OK,
        'next_url': next_url,
        'campaign': None,
        'must_login_warning': False,
    }

    # login or register with campaign parameter
    if campaign:
        if validate_campaign(campaign):
            # GET `/register` or '/login` with `campaign=institution`
            # unlike other campaigns, institution login serves as an alternative for authentication
            if campaign == 'institution':
                if next_url is None:
                    next_url = web_url_for('dashboard', _absolute=True)
                data['status_code'] = http.FOUND
                if auth.logged_in:
                    data['next_url'] = next_url
                else:
                    data['next_url'] = cas.get_login_url(next_url, campaign='institution')
            # for non-institution campaigns
            else:
                destination = next_url if next_url else campaigns.campaign_url_for(campaign)
                if auth.logged_in:
                    # if user is already logged in, go to the campaign landing page
                    data['status_code'] = http.FOUND
                    data['next_url'] = destination
                else:
                    # if user is logged out, go to the osf register page with campaign context
                    if login:
                        # `GET /login?campaign=...`
                        data['next_url'] = web_url_for('auth_register', campaign=campaign, next=destination)
                    else:
                        # `GET /register?campaign=...`
                        data['campaign'] = campaign
                        if campaigns.is_proxy_login(campaign):
                            data['next_url'] = web_url_for(
                                'auth_login',
                                next=destination,
                                _absolute=True
                            )
                        else:
                            data['next_url'] = destination
        else:
            # invalid campaign, inform sentry and redirect to non-campaign sign up or sign in
            redirect_view = 'auth_login' if login else 'auth_register'
            data['status_code'] = http.FOUND
            data['next_url'] = web_url_for(redirect_view, campaigns=None, next=next_url)
            data['campaign'] = None
            sentry.log_message(
                '{} is not a valid campaign. Please add it if this is a new one'.format(campaign)
            )
    # login or register with next parameter
    elif next_url:
        if logout:
            # handle `claim_user_registered`
            data['next_url'] = next_url
            if auth.logged_in:
                # log user out and come back
                data['status_code'] = 'auth_logout'
            else:
                # after logout, land on the register page with "must_login" warning
                data['status_code'] = http.OK
                data['must_login_warning'] = True
        elif auth.logged_in:
            # if user is already logged in, redirect to `next_url`
            data['status_code'] = http.FOUND
            data['next_url'] = next_url
        elif login:
            # `/login?next=next_url`: go to CAS login page with current request url as service url
            data['status_code'] = http.FOUND
            data['next_url'] = cas.get_login_url(request.url)
        else:
            # `/register?next=next_url`: land on OSF register page with request url as next url
            data['status_code'] = http.OK
            data['next_url'] = request.url
    else:
        # `/login/` or `/register/` without any parameter
        if auth.logged_in:
            data['status_code'] = http.FOUND
        data['next_url'] = web_url_for('dashboard', _absolute=True)

    return data
Exemplo n.º 60
0
 def web_url_for(self, view_name, _absolute=False, _guid=False, *args, **kwargs):
     return web_url_for(view_name, pid=self._id,
                        _absolute=_absolute, _guid=_guid, *args, **kwargs)