def test_provider_down(self): # Create a 500 error _prepare_mock_500_error() user = UserFactory() # Fake a request context for the callback with self.app.app.test_request_context( path="/oauth/callback/mock2/", query_string="code=mock_code&state=mock_state"): # make sure the user is logged in authenticate(user=user, response=None) session = get_session() session.data['oauth_states'] = { self.provider.short_name: { 'state': 'mock_state', }, } session.save() # do the key exchange with assert_raises(HTTPError) as error_raised: self.provider.auth_callback(user=user) assert_equal( error_raised.exception.code, 503, )
def osfstorage_download(file_node, payload, **kwargs): # Set user ID in session data for checking if user is contributor # to project. user_id = payload.get('user') if user_id: current_session = get_session() current_session.data['auth_user_id'] = user_id current_session.save() if not request.args.get('version'): version_id = None else: try: version_id = int(request.args['version']) except ValueError: raise make_error( http_status.HTTP_400_BAD_REQUEST, message_short='Version must be an integer if not specified') version = file_node.get_version(version_id, required=True) file_version_thru = version.get_basefilenode_version(file_node) name = file_version_thru.version_name if file_version_thru else file_node.name return { 'data': { 'name': name, 'path': version.location_hash, }, 'settings': { osf_storage_settings.WATERBUTLER_RESOURCE: version.location[osf_storage_settings.WATERBUTLER_RESOURCE], }, }
def test_callback(self): # Exchange temporary credentials for permanent credentials # Mock the exchange of the code for an access token _prepare_mock_oauth2_handshake_response() user = UserFactory() # Fake a request context for the callback with self.app.app.test_request_context( path="/oauth/callback/mock2/", query_string="code=mock_code&state=mock_state") as ctx: # make sure the user is logged in authenticate(user=user, response=None) session = get_session() session.data['oauth_states'] = { self.provider.short_name: { 'state': 'mock_state', }, } session.save() # do the key exchange self.provider.auth_callback(user=user) account = ExternalAccount.find_one() assert_equal(account.oauth_key, 'mock_access_token') assert_equal(account.provider_id, 'mock_provider_id')
def test_start_flow(self): # Request temporary credentials from provider, provide auth redirect responses.add(responses.POST, 'http://mock1a.com/request', body='{"oauth_token_secret": "temp_secret", ' '"oauth_token": "temp_token", ' '"oauth_callback_confirmed": "true"}', status=200, content_type='application/json') with self.app.app.test_request_context('/oauth/connect/mock1a/'): # make sure the user is logged in authenticate(user=self.user, response=None) # auth_url is a property method - it calls out to the external # service to get a temporary key and secret before returning the # auth url url = self.provider.auth_url # The URL to which the user would be redirected assert_equal(url, "http://mock1a.com/auth?oauth_token=temp_token") session = get_session() # Temporary credentials are added to the session creds = session.data['oauth_states'][self.provider.short_name] assert_equal(creds['token'], 'temp_token') assert_equal(creds['secret'], 'temp_secret')
def test_start_flow(self): # Request temporary credentials from provider, provide auth redirect httpretty.register_uri(httpretty.POST, 'http://mock1a.com/request', body='{"oauth_token_secret": "temp_secret", ' '"oauth_token": "temp_token", ' '"oauth_callback_confirmed": "true"}', status=200, content_type='application/json') with self.app.app.test_request_context('/oauth/connect/mock1a/'): # make sure the user is logged in authenticate(user=self.user, access_token=None, response=None) # auth_url is a property method - it calls out to the external # service to get a temporary key and secret before returning the # auth url url = self.provider.auth_url # The URL to which the user would be redirected assert_equal(url, "http://mock1a.com/auth?oauth_token=temp_token") session = get_session() # Temporary credentials are added to the session creds = session.data['oauth_states'][self.provider.short_name] assert_equal(creds['token'], 'temp_token') assert_equal(creds['secret'], 'temp_secret')
def test_provider_down(self): # Create a 500 error _prepare_mock_500_error() user = UserFactory() # Fake a request context for the callback with self.app.app.test_request_context( path="/oauth/callback/mock2/", query_string="code=mock_code&state=mock_state" ): # make sure the user is logged in authenticate(user=user, access_token=None, response=None) session = get_session() session.data['oauth_states'] = { self.provider.short_name: { 'state': 'mock_state', }, } session.save() # do the key exchange with assert_raises(HTTPError) as error_raised: self.provider.auth_callback(user=user) assert_equal( error_raised.exception.code, 503, )
def test_callback(self): # Exchange temporary credentials for permanent credentials # Mock the exchange of the code for an access token _prepare_mock_oauth2_handshake_response() user = UserFactory() # Fake a request context for the callback with self.app.app.test_request_context( path="/oauth/callback/mock2/", query_string="code=mock_code&state=mock_state" ): # make sure the user is logged in authenticate(user=self.user, access_token=None, response=None) session = get_session() session.data['oauth_states'] = { self.provider.short_name: { 'state': 'mock_state', }, } session.save() # do the key exchange self.provider.auth_callback(user=user) account = ExternalAccount.find_one() assert_equal(account.oauth_key, 'mock_access_token') assert_equal(account.provider_id, 'mock_provider_id')
def osfstorage_download(file_node, payload, node_addon, **kwargs): # Set user ID in session data for checking if user is contributor # to project. user_id = payload.get("user") if user_id: current_session = get_session() current_session.data["auth_user_id"] = user_id if not request.args.get("version"): version_id = None else: try: version_id = int(request.args["version"]) except ValueError: raise make_error(httplib.BAD_REQUEST, message_short="Version must be an integer if not specified") version = file_node.get_version(version_id, required=True) if request.args.get("mode") not in ("render",): utils.update_analytics(node_addon.owner, file_node._id, int(version.identifier) - 1) return { "data": {"name": file_node.name, "path": version.location_hash}, "settings": { osf_storage_settings.WATERBUTLER_RESOURCE: version.location[osf_storage_settings.WATERBUTLER_RESOURCE] }, }
def osfstorage_download(file_node, payload, node_addon, **kwargs): # Set user ID in session data for checking if user is contributor # to project. user_id = payload.get('user') if user_id: current_session = get_session() current_session.data['auth_user_id'] = user_id current_session.save() if not request.args.get('version'): version_id = None else: try: version_id = int(request.args['version']) except ValueError: raise make_error(httplib.BAD_REQUEST, message_short='Version must be an integer if not specified') version = file_node.get_version(version_id, required=True) if request.args.get('mode') not in ('render', ): utils.update_analytics(node_addon.owner, file_node._id, int(version.identifier) - 1) return { 'data': { 'name': file_node.name, 'path': version.location_hash, }, 'settings': { osf_storage_settings.WATERBUTLER_RESOURCE: version.location[osf_storage_settings.WATERBUTLER_RESOURCE], }, }
def osfstorage_download(file_node, payload, node_addon, **kwargs): # Set user ID in session data for checking if user is contributor # to project. user_id = payload.get('user') if user_id: current_session = get_session() current_session.data['auth_user_id'] = user_id if not request.args.get('version'): version_id = None else: try: version_id = int(request.args['version']) except ValueError: raise make_error( httplib.BAD_REQUEST, message_short='Version must be an integer if not specified') version = file_node.get_version(version_id, required=True) if request.args.get('mode') not in ('render', ): utils.update_analytics(node_addon.owner, file_node._id, int(version.identifier) - 1) return { 'data': { 'name': file_node.name, 'path': version.location_hash, }, 'settings': { osf_storage_settings.WATERBUTLER_RESOURCE: version.location[osf_storage_settings.WATERBUTLER_RESOURCE], }, }
def test_multiple_users_associated(self): # Create only one ExternalAccount for multiple OSF users # # For some providers (ex: GitHub), the act of completing the OAuth flow # revokes previously generated credentials. In addition, there is often no # way to know the user's id on the external service until after the flow # has completed. # # Having only one ExternalAccount instance per account on the external # service means that connecting subsequent OSF users to the same external # account will not invalidate the credentials used by the OSF for users # already associated. user_a = UserFactory() external_account = ExternalAccountFactory( provider='mock2', provider_id='mock_provider_id', provider_name='Mock Provider', ) user_a.external_accounts.append(external_account) user_a.save() user_b = UserFactory() # Mock the exchange of the code for an access token _prepare_mock_oauth2_handshake_response() # Fake a request context for the callback with self.app.app.test_request_context( path="/oauth/callback/mock2/", query_string="code=mock_code&state=mock_state" ) as ctx: # make sure the user is logged in authenticate(user=user_b, access_token=None, response=None) session = get_session() session.data['oauth_states'] = { self.provider.short_name: { 'state': 'mock_state', }, } session.save() # do the key exchange self.provider.auth_callback(user=user_b) user_a.reload() user_b.reload() external_account.reload() assert_equal( user_a.external_accounts, user_b.external_accounts, ) assert_equal( ExternalAccount.find().count(), 1 )
def test_multiple_users_associated(self): # Create only one ExternalAccount for multiple OSF users # # For some providers (ex: GitHub), the act of completing the OAuth flow # revokes previously generated credentials. In addition, there is often no # way to know the user's id on the external service until after the flow # has completed. # # Having only one ExternalAccount instance per account on the external # service means that connecting subsequent OSF users to the same external # account will not invalidate the credentials used by the OSF for users # already associated. user_a = UserFactory() external_account = ExternalAccountFactory( provider='mock2', provider_id='mock_provider_id', provider_name='Mock Provider', ) user_a.external_accounts.append(external_account) user_a.save() user_b = UserFactory() # Mock the exchange of the code for an access token _prepare_mock_oauth2_handshake_response() # Fake a request context for the callback with self.app.app.test_request_context( path="/oauth/callback/mock2/", query_string="code=mock_code&state=mock_state") as ctx: # make sure the user is logged in authenticate(user=user_b, response=None) session = get_session() session.data['oauth_states'] = { self.provider.short_name: { 'state': 'mock_state', }, } session.save() # do the key exchange self.provider.auth_callback(user=user_b) user_a.reload() user_b.reload() external_account.reload() assert_equal( user_a.external_accounts, user_b.external_accounts, ) assert_equal(ExternalAccount.find().count(), 1)
def external_login_email_get(): """ Landing view for first-time oauth-login user to enter their email address. HTTP Method: GET """ form = ResendConfirmationForm(request.form) session = get_session() if not session.is_external_first_login: raise HTTPError(http.UNAUTHORIZED) external_id_provider = session.data['auth_user_external_id_provider'] return {'form': form, 'external_id_provider': external_id_provider}
def auth_url(self): """The URL to begin the OAuth dance. This property method has side effects - it at least adds temporary information to the session so that callbacks can be associated with the correct user. For OAuth1, it calls the provider to obtain temporary credentials to start the flow. """ session = get_session() # create a dict on the session object if it's not already there if session.data.get("oauth_states") is None: session.data['oauth_states'] = {} if self._oauth_version == OAUTH2: # build the URL oauth = OAuth2Session( self.client_id, redirect_uri=web_url_for('oauth_callback', service_name=self.short_name, _absolute=True), scope=self.default_scopes, ) url, state = oauth.authorization_url(self.auth_url_base) # save state token to the session for confirmation in the callback session.data['oauth_states'][self.short_name] = {'state': state} elif self._oauth_version == OAUTH1: # get a request token oauth = OAuth1Session( client_key=self.client_id, client_secret=self.client_secret, ) # request temporary credentials from the provider response = oauth.fetch_request_token(self.request_token_url) # store them in the session for use in the callback session.data['oauth_states'][self.short_name] = { 'token': response.get('oauth_token'), 'secret': response.get('oauth_token_secret'), } url = oauth.authorization_url(self.auth_url_base) return url
def get_request_and_user_id(): """ Fetch a request and user id from either a Django or Flask request. """ # TODO: This should be consolidated into framework from framework.sessions import get_session req = get_current_request() user_id = None if isinstance(req, FlaskRequest): session = get_session() user_id = session.data.get('auth_user_id') elif hasattr(req, 'user'): # admin module can return a user w/o an id user_id = getattr(req.user, '_id', None) return req, user_id
def get_request_and_user_id(): """ Fetch a request and user id from either a Django or Flask request. """ # TODO: This should be consolidated into framework from framework.sessions import get_session req = get_cache_key() user_id = None if isinstance(req, FlaskRequest): session = get_session() user_id = session.data.get('auth_user_id') elif hasattr(req, 'user'): # admin module can return a user w/o an id user_id = getattr(req.user, '_id', None) return req, user_id
def test_func(self): """check user permissions""" institution_id = None addon_name = self.kwargs.get('addon_name') session_data = {} try: session = get_session() session_data = session.data except RuntimeError: print('Unable to access session data') if 'oauth_states' in session_data: institution_id = int(session_data['oauth_states'][addon_name]['institution_id']) elif 'institution_id' in self.kwargs: institution_id = int(self.kwargs.get('institution_id')) return self.has_auth(institution_id)
def external_login_email_get(): """ Landing view for first-time oauth-login user to enter their email address. HTTP Method: GET """ form = ResendConfirmationForm(request.form) session = get_session() if not session.is_external_first_login: raise HTTPError(http.UNAUTHORIZED) external_id_provider = session.data['auth_user_external_id_provider'] return { 'form': form, 'external_id_provider': external_id_provider }
def auth_url(self): """The URL to begin the OAuth dance. Accessing this property will create an ``ExternalAccount`` if one is not already attached to the instance, in order to store the state token and scope.""" session = get_session() # create a dict on the session object if it's not already there if session.data.get("oauth_states") is None: session.data['oauth_states'] = {} if self._oauth_version == OAUTH2: # build the URL oauth = OAuth2Session( self.client_id, redirect_uri=web_url_for('oauth_callback', service_name=self.short_name, _absolute=True), scope=self.default_scopes, ) url, state = oauth.authorization_url(self.auth_url_base) # save state token to the account instance to be available in callback session.data['oauth_states'][self.short_name] = {'state': state} elif self._oauth_version == OAUTH1: # get a request token oauth = OAuth1Session( client_key=self.client_id, client_secret=self.client_secret, ) response = oauth.fetch_request_token(self.request_token_url) session.data['oauth_states'][self.short_name] = { 'token': response.get('oauth_token'), 'secret': response.get('oauth_token_secret'), } url = oauth.authorization_url(self.auth_url_base) return url
def test_callback(self): # Exchange temporary credentials for permanent credentials # mock a successful call to the provider to exchange temp keys for # permanent keys httpretty.register_uri( httpretty.POST, 'http://mock1a.com/callback', body=( 'oauth_token=perm_token' '&oauth_token_secret=perm_secret' '&oauth_callback_confirmed=true' ), ) user = UserFactory() # Fake a request context for the callback ctx = self.app.app.test_request_context( path='/oauth/callback/mock1a/', query_string='oauth_token=temp_key&oauth_verifier=mock_verifier', ) with ctx: # make sure the user is logged in authenticate(user=user, access_token=None, response=None) session = get_session() session.data['oauth_states'] = { self.provider.short_name: { 'token': 'temp_key', 'secret': 'temp_secret', }, } session.save() # do the key exchange self.provider.auth_callback(user=user) account = ExternalAccount.find_one() assert_equal(account.oauth_key, 'perm_token') assert_equal(account.oauth_secret, 'perm_secret') assert_equal(account.provider_id, 'mock_provider_id') assert_equal(account.provider_name, 'Mock OAuth 1.0a Provider')
def test_start_flow(self): # Generate the appropriate URL and state token with self.app.app.test_request_context("/oauth/connect/mock2/"): # make sure the user is logged in authenticate(user=self.user, access_token=None, response=None) # auth_url is a property method - it calls out to the external # service to get a temporary key and secret before returning the # auth url url = self.provider.auth_url session = get_session() # Temporary credentials are added to the session creds = session.data['oauth_states'][self.provider.short_name] assert_in('state', creds) # The URL to which the user would be redirected parsed = urlparse.urlparse(url) params = urlparse.parse_qs(parsed.query) # check parameters assert_equal( params, { 'state': [creds['state']], 'response_type': ['code'], 'client_id': [self.provider.client_id], 'redirect_uri': [ web_url_for('oauth_callback', service_name=self.provider.short_name, _absolute=True) ] } ) # check base URL assert_equal( url.split("?")[0], "https://mock2.com/auth", )
def test_callback(self): # Exchange temporary credentials for permanent credentials # mock a successful call to the provider to exchange temp keys for # permanent keys responses.add( responses.POST, 'http://mock1a.com/callback', body=('oauth_token=perm_token' '&oauth_token_secret=perm_secret' '&oauth_callback_confirmed=true'), ) user = UserFactory() # Fake a request context for the callback ctx = self.app.app.test_request_context( path='/oauth/callback/mock1a/', query_string='oauth_token=temp_key&oauth_verifier=mock_verifier', ) with ctx: # make sure the user is logged in authenticate(user=user, response=None) session = get_session() session.data['oauth_states'] = { self.provider.short_name: { 'token': 'temp_key', 'secret': 'temp_secret', }, } session.save() # do the key exchange self.provider.auth_callback(user=user) account = ExternalAccount.find_one() assert_equal(account.oauth_key, 'perm_token') assert_equal(account.oauth_secret, 'perm_secret') assert_equal(account.provider_id, 'mock_provider_id') assert_equal(account.provider_name, 'Mock OAuth 1.0a Provider')
def test_start_flow(self): # Generate the appropriate URL and state token with self.app.app.test_request_context("/oauth/connect/mock2/") as ctx: # make sure the user is logged in authenticate(user=self.user, response=None) # auth_url is a property method - it calls out to the external # service to get a temporary key and secret before returning the # auth url url = self.provider.auth_url session = get_session() # Temporary credentials are added to the session creds = session.data['oauth_states'][self.provider.short_name] assert_in('state', creds) # The URL to which the user would be redirected parsed = urlparse.urlparse(url) params = urlparse.parse_qs(parsed.query) # check parameters assert_equal( params, { 'state': [creds['state']], 'response_type': ['code'], 'client_id': [self.provider.client_id], 'redirect_uri': [ web_url_for('oauth_callback', service_name=self.provider.short_name, _absolute=True) ] }) # check base URL assert_equal( url.split("?")[0], "https://mock2.com/auth", )
def external_login_email_post(): """ View to handle email submission for first-time oauth-login user. HTTP Method: POST """ form = ResendConfirmationForm(request.form) session = get_session() if not session.is_external_first_login: raise HTTPError(http.UNAUTHORIZED) external_id_provider = session.data['auth_user_external_id_provider'] external_id = session.data['auth_user_external_id'] fullname = session.data['auth_user_fullname'] service_url = session.data['service_url'] # TODO: @cslzchen use user tags instead of destination destination = 'dashboard' for campaign in campaigns.get_campaigns(): if campaign != 'institution': # Handle different url encoding schemes between `furl` and `urlparse/urllib`. # OSF use `furl` to parse service url during service validation with CAS. However, `web_url_for()` uses # `urlparse/urllib` to generate service url. `furl` handles `urlparser/urllib` generated urls while ` but # not vice versa. campaign_url = furl.furl(campaigns.campaign_url_for(campaign)).url external_campaign_url = furl.furl( campaigns.external_campaign_url_for(campaign)).url if campaigns.is_proxy_login(campaign): # proxy campaigns: OSF Preprints and branded ones if check_service_url_with_proxy_campaign( str(service_url), campaign_url, external_campaign_url): destination = campaign # continue to check branded preprints even service url matches osf preprints if campaign != 'osf-preprints': break elif service_url.startswith(campaign_url): # osf campaigns: OSF Prereg and ERPC destination = campaign break if form.validate(): clean_email = form.email.data user = get_user(email=clean_email) external_identity = { external_id_provider: { external_id: None, }, } try: ensure_external_identity_uniqueness(external_id_provider, external_id, user) except ValidationError as e: raise HTTPError(http.FORBIDDEN, e.message) if user: # 1. update user oauth, with pending status external_identity[external_id_provider][external_id] = 'LINK' if external_id_provider in user.external_identity: user.external_identity[external_id_provider].update( external_identity[external_id_provider]) else: user.external_identity.update(external_identity) # 2. add unconfirmed email and send confirmation email user.add_unconfirmed_email(clean_email, external_identity=external_identity) user.save() send_confirm_email(user, clean_email, external_id_provider=external_id_provider, external_id=external_id, destination=destination) # 3. notify user message = language.EXTERNAL_LOGIN_EMAIL_LINK_SUCCESS.format( external_id_provider=external_id_provider, email=user.username) kind = 'success' # 4. remove session and osf cookie remove_session(session) else: # 1. create unconfirmed user with pending status external_identity[external_id_provider][external_id] = 'CREATE' user = OSFUser.create_unconfirmed( username=clean_email, password=None, fullname=fullname, external_identity=external_identity, campaign=None) # TODO: [#OSF-6934] update social fields, verified social fields cannot be modified user.save() # 3. send confirmation email send_confirm_email(user, user.username, external_id_provider=external_id_provider, external_id=external_id, destination=destination) # 4. notify user message = language.EXTERNAL_LOGIN_EMAIL_CREATE_SUCCESS.format( external_id_provider=external_id_provider, email=user.username) kind = 'success' # 5. remove session remove_session(session) status.push_status_message(message, kind=kind, trust=False) else: forms.push_errors_to_status(form.errors) # Don't go anywhere return {'form': form, 'external_id_provider': external_id_provider}
def get_session_data(): try: return get_session().data except (RuntimeError, AttributeError): return {}
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. """ session = get_session() # 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()
def external_login_email_post(): """ View to handle email submission for first-time oauth-login user. HTTP Method: POST """ form = ResendConfirmationForm(request.form) session = get_session() if not session.is_external_first_login: raise HTTPError(http.UNAUTHORIZED) external_id_provider = session.data['auth_user_external_id_provider'] external_id = session.data['auth_user_external_id'] fullname = session.data['auth_user_fullname'] if form.validate(): clean_email = form.email.data user = get_user(email=clean_email) external_identity = { external_id_provider: { external_id: None, }, } try: ensure_external_identity_uniqueness(external_id_provider, external_id, user) except ValidationError as e: raise HTTPError(http.FORBIDDEN, e.message) if user: # 1. update user oauth, with pending status external_identity[external_id_provider][external_id] = 'LINK' if external_id_provider in user.external_identity: user.external_identity[external_id_provider].update(external_identity[external_id_provider]) else: user.external_identity.update(external_identity) # 2. add unconfirmed email and send confirmation email user.add_unconfirmed_email(clean_email, external_identity=external_identity) user.save() send_confirm_email(user, clean_email, external_id_provider=external_id_provider, external_id=external_id) # 3. notify user message = language.EXTERNAL_LOGIN_EMAIL_LINK_SUCCESS.format( external_id_provider=external_id_provider, email=user.username ) kind = 'success' # 4. remove session and osf cookie remove_session(session) else: # 1. create unconfirmed user with pending status external_identity[external_id_provider][external_id] = 'CREATE' user = User.create_unconfirmed( username=clean_email, password=str(uuid.uuid4()), fullname=fullname, external_identity=external_identity, campaign=None ) # TODO: [#OSF-6934] update social fields, verified social fields cannot be modified user.save() # 3. send confirmation email send_confirm_email(user, user.username, external_id_provider=external_id_provider, external_id=external_id) # 4. notify user message = language.EXTERNAL_LOGIN_EMAIL_CREATE_SUCCESS.format( external_id_provider=external_id_provider, email=user.username ) kind = 'success' # 5. remove session remove_session(session) status.push_status_message(message, kind=kind, trust=False) else: forms.push_errors_to_status(form.errors) # Don't go anywhere return { 'form': form, 'external_id_provider': external_id_provider }
def external_login_email_post(): """ View to handle email submission for first-time oauth-login user. HTTP Method: POST """ form = ResendConfirmationForm(request.form) session = get_session() if not session.is_external_first_login: raise HTTPError(http.UNAUTHORIZED) external_id_provider = session.data['auth_user_external_id_provider'] external_id = session.data['auth_user_external_id'] fullname = session.data['auth_user_fullname'] service_url = session.data['service_url'] destination = 'dashboard' for campaign in campaigns.get_campaigns(): if campaign != 'institution': # Handle different url encoding schemes between `furl` and `urlparse/urllib`. # OSF use `furl` to parse service url during service validation with CAS. However, `web_url_for()` uses # `urlparse/urllib` to generate service url. `furl` handles `urlparser/urllib` generated urls while ` but # not vice versa. campaign_url = furl.furl(campaigns.campaign_url_for(campaign)).url if campaigns.is_proxy_login(campaign): # proxy campaigns: OSF Preprints and branded ones if check_service_url_with_proxy_campaign(service_url, campaign_url): destination = campaign # continue to check branded preprints even service url matches osf preprints if campaign != 'osf-preprints': break elif service_url.startswith(campaign_url): # osf campaigns: OSF Prereg and ERPC destination = campaign break if form.validate(): clean_email = form.email.data user = get_user(email=clean_email) external_identity = { external_id_provider: { external_id: None, }, } try: ensure_external_identity_uniqueness(external_id_provider, external_id, user) except ValidationError as e: raise HTTPError(http.FORBIDDEN, e.message) if user: # 1. update user oauth, with pending status external_identity[external_id_provider][external_id] = 'LINK' if external_id_provider in user.external_identity: user.external_identity[external_id_provider].update(external_identity[external_id_provider]) else: user.external_identity.update(external_identity) # 2. add unconfirmed email and send confirmation email user.add_unconfirmed_email(clean_email, external_identity=external_identity) user.save() send_confirm_email( user, clean_email, external_id_provider=external_id_provider, external_id=external_id, destination=destination ) # 3. notify user message = language.EXTERNAL_LOGIN_EMAIL_LINK_SUCCESS.format( external_id_provider=external_id_provider, email=user.username ) kind = 'success' # 4. remove session and osf cookie remove_session(session) else: # 1. create unconfirmed user with pending status external_identity[external_id_provider][external_id] = 'CREATE' user = User.create_unconfirmed( username=clean_email, password=str(uuid.uuid4()), fullname=fullname, external_identity=external_identity, campaign=None ) # TODO: [#OSF-6934] update social fields, verified social fields cannot be modified user.save() # 3. send confirmation email send_confirm_email( user, user.username, external_id_provider=external_id_provider, external_id=external_id, destination=destination ) # 4. notify user message = language.EXTERNAL_LOGIN_EMAIL_CREATE_SUCCESS.format( external_id_provider=external_id_provider, email=user.username ) kind = 'success' # 5. remove session remove_session(session) status.push_status_message(message, kind=kind, trust=False) else: forms.push_errors_to_status(form.errors) # Don't go anywhere return { 'form': form, 'external_id_provider': external_id_provider }
def auth_callback(self, user): session = get_session() 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') 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') 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: raise HTTPError(http.SERVICE_UNAVAILABLE) info = self._default_handle_callback(response) info.update(self.handle_callback(response)) try: self.account = ExternalAccount( provider=self.short_name, provider_id=info['provider_id'], ) self.account.save() except KeyExistsException: self.account = ExternalAccount.find_one( Q('provider', 'eq', self.short_name) & Q('provider_id', 'eq', info['provider_id']) ) assert self.account is not None # 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() if self.account not in user.external_accounts: user.external_accounts.append(self.account) user.save()
def get_session_data(): try: return get_session().data except RuntimeError: return {}