def login(remote_app): """ Redirect user to remote application for authentication. This function redirects the user to the IdP for authorization. After having authorized the IdP redirects the user back to this web application as configured in your ``saml_path``. :param remote_app: (str) Identity provider key. :returns: (flask.Response) Return redirect response to IdP or abort in case of failure. """ if remote_app not in current_app.config['SHIBBOLETH_IDENTITY_PROVIDERS']: return abort(404) # Store next parameter in state token next_param = get_safe_redirect_target(arg='next') if not next_param: next_param = '/' state_token = serializer.dumps({ 'app': remote_app, 'next': next_param, 'sid': _create_identifier() }) # req = prepare_flask_request(request) try: auth = init_saml_auth(request, remote_app) except Exception: return abort(500) return redirect(auth.login(state_token))
def gitlab_connect(): r"""Endpoint to init the REANA connection to GitLab. --- get: summary: Initiate connection to GitLab. operationId: gitlab_connect description: >- Initiate connection to GitLab to authorize accessing the authenticated user's API. responses: 302: description: >- Redirection to GitLab site. """ # Get redirect target in safe manner. next_param = get_safe_redirect_target() # Create a JSON Web Token state_token = serializer.dumps({ "next": next_param, "sid": _create_identifier(), }) params = { "client_id": REANA_GITLAB_OAUTH_APP_ID, "redirect_uri": url_for(".gitlab_oauth", _external=True), "response_type": "code", "scope": "api", "state": state_token, } req = requests.PreparedRequest() req.prepare_url(REANA_GITLAB_URL + "/oauth/authorize", params) return redirect(req.url), 302
def gitlab_connect(): r"""Endpoint to init the REANA connection to GitLab. --- get: summary: Initiate connection to GitLab. operationId: gitlab_connect description: >- Initiate connection to GitLab to authorize accessing the authenticated user's API. responses: 302: description: >- Redirection to GitLab site. """ # Get redirect target in safe manner. next_param = get_safe_redirect_target() # Create a JSON Web Token state_token = serializer.dumps({ 'next': next_param, 'sid': _create_identifier(), }) params = { 'client_id': REANA_GITLAB_OAUTH_APP_ID, 'redirect_uri': url_for('.gitlab_oauth', _external=True), 'response_type': 'code', 'scope': 'api', 'state': state_token } req = requests.PreparedRequest() req.prepare_url(REANA_GITLAB_URL + '/oauth/authorize', params) return redirect(req.url), 302
def authorized(remote_app=None): """ Authorize handler callback. This function is called when the user is redirected from the IdP to the web application. It handles the authorization. :param remote_app: (str) Identity provider key :returns: (flask.Response) Return redirect response or abort in case of failure. """ # Logout user if already logged if current_user.is_authenticated: logout_user() # Configuration not found for given identity provider if remote_app not in current_app.config['SHIBBOLETH_IDENTITY_PROVIDERS']: return abort(404) # Init SAML auth req = prepare_flask_request(request) try: auth = init_saml_auth(req, remote_app) except Exception: return abort(500) # Process response errors = [] try: auth.process_response() except OneLogin_Saml2_Error: return abort(400) errors = auth.get_errors() if not errors and auth.is_authenticated(): if 'RelayState' in request.form: # Get state token stored in RelayState state_token = request.form['RelayState'] try: if not state_token: raise ValueError # Check authenticity and integrity of state and decode the # values. state = serializer.loads(state_token) # Verify that state is for this session, app and that next # parameter have not been modified. if (state['sid'] != _create_identifier() or state['app'] != remote_app): raise ValueError # Store next url set_session_next_url(remote_app, state['next']) except (ValueError, BadData): if current_app.config.get( 'OAUTHCLIENT_STATE_ENABLED', True) or ( not (current_app.debug or current_app.testing)): return abort(400) return authorized_signup_handler(auth, remote_app) return abort(403)
def test_authorized(monkeypatch, app, client, roles, user_without_role, valid_attributes): """Test authorized view.""" # Test unexisting identity provider app.config.update(SHIBBOLETH_SERVICE_PROVIDER=dict( debug=True, strict=True, x509cert='./data/shibboleth/sp.pem', private_key='./data/shibboleth/sp.key')) assert client.post('/shibboleth/authorized/not_exists').status_code == 404 # Test exception in SAML authentication init monkeypatch.setattr( 'sonar.modules.shibboleth_authenticator.views.client.init_saml_auth', lambda: Exception) assert client.post('/shibboleth/authorized/idp').status_code == 500 class MockAuth(object): """Mock auth.""" @staticmethod def process_response(): """Process response for authentication.""" raise OneLogin_Saml2_Error(message='') @staticmethod def get_errors(): """Get auth errors.""" return ['Error'] @staticmethod def is_authenticated(): """Is user authenticated.""" return True @staticmethod def get_attributes(): """Return auth attributes.""" return valid_attributes # Test error in authentication process mock_auth = MockAuth() monkeypatch.setattr( 'sonar.modules.shibboleth_authenticator.views.client.init_saml_auth', lambda req, remote_app: mock_auth) assert client.post('/shibboleth/authorized/idp').status_code == 400 # Test when user is authenticated class MockUser(object): """Mock user.""" def is_authenticated(self): """Return if user is authenticated.""" return True mock_user = MockUser() monkeypatch.setattr( 'sonar.modules.shibboleth_authenticator.views.client.current_user', mock_user) assert client.post('/shibboleth/authorized/idp').status_code == 400 # Test errors in authentication (but no exception) mock_auth.process_response = lambda: None assert client.post( '/shibboleth/authorized/idp', data=dict( SAMLResponse=_load_file('valid_saml_response'))).status_code == 403 # Test valid authentication mock_auth.get_errors = lambda: [] from sonar.modules.shibboleth_authenticator.views.client import serializer next_url = '/test/redirect' state = serializer.dumps({ 'app': 'idp', 'sid': _create_identifier(), 'next': next_url, }) assert client.post('/shibboleth/authorized/idp', data=dict( SAMLResponse=_load_file('valid_saml_response'), RelayState=state)).status_code == 302 # Test error in relay state token monkeypatch.setattr( 'sonar.modules.shibboleth_authenticator.views.client' '._create_identifier', lambda: 'test') assert client.post('/shibboleth/authorized/idp', data=dict( SAMLResponse=_load_file('valid_saml_response'), RelayState=state)).status_code == 400 class MockRequest(object): """Mock request.""" url = 'https://sonar.ch/test/page?parameter=test' host = 'sonar.ch' scheme = 'https' path = '/test/page' args = dict(parameter='test') form = dict(RelayState=None) mock_request = MockRequest() # Test error when no relay state token found monkeypatch.setattr( 'sonar.modules.shibboleth_authenticator.views.client.request', mock_request) assert client.post( '/shibboleth/authorized/idp', data=dict( SAMLResponse=_load_file('valid_saml_response'))).status_code == 400
def gitlab_oauth(): # noqa r"""Endpoint to authorize REANA on GitLab. --- get: summary: Get access token from GitLab operationId: gitlab_oauth description: >- Authorize REANA on GitLab. produces: - application/json responses: 200: description: >- Ping succeeded. schema: type: object properties: message: type: string status: type: string examples: application/json: message: OK status: 200 201: description: >- Authorization succeeded. GitLab secret created. schema: type: object properties: message: type: string status: type: string examples: application/json: message: GitLab secret created status: 201 403: description: >- Request failed. User token not valid. examples: application/json: { "message": "Token is not valid." } 500: description: >- Request failed. Internal controller error. """ try: if current_user.is_authenticated: user = _get_user_from_invenio_user(current_user.email) else: user = get_user_from_token(request.args.get("access_token")) if "code" in request.args: # Verifies state parameter and obtain next url state_token = request.args.get("state") assert state_token # Checks authenticity and integrity of state and decodes the value. state = serializer.loads(state_token) # Verifies that state is for this session and that next parameter # has not been modified. assert state["sid"] == _create_identifier() # Stores next URL next_url = state["next"] gitlab_code = request.args.get("code") params = { "client_id": REANA_GITLAB_OAUTH_APP_ID, "client_secret": REANA_GITLAB_OAUTH_APP_SECRET, "redirect_uri": url_for(".gitlab_oauth", _external=True), "code": gitlab_code, "grant_type": "authorization_code", } gitlab_response = requests.post(REANA_GITLAB_URL + "/oauth/token", data=params).content secrets_store = REANAUserSecretsStore(str(user.id_)) secrets_store.add_secrets(_format_gitlab_secrets(gitlab_response), overwrite=True) return redirect(next_url), 201 else: return jsonify({"message": "OK"}), 200 except ValueError: return jsonify({"message": "Token is not valid."}), 403 except (AssertionError, BadData): return jsonify({"message": "State param is invalid."}), 403 except Exception as e: logging.error(traceback.format_exc()) return jsonify({"message": str(e)}), 500