Beispiel #1
0
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))
Beispiel #2
0
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
Beispiel #3
0
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
Beispiel #4
0
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
Beispiel #6
0
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