Beispiel #1
0
def _get_valid_access_token(app, session, request):
    """
    Return a valid access token. If at any point access token is determined
    invalid, this will return None.
    """
    access_token = request.cookies.get(config["ACCESS_TOKEN_COOKIE_NAME"],
                                       None)

    if not access_token:
        return None

    try:
        valid_access_token = validate_jwt(access_token, purpose="access")
    except Exception as exc:
        return None

    # try to get user, exception means they're not logged in
    try:
        user = get_current_user(flask_session=session)
    except Unauthorized:
        return None

    # check that the current user is the one from the session and access_token
    user_sess_id = _get_user_id_from_session(session)
    token_user_id = _get_user_id_from_access_token(valid_access_token)

    if user.id != user_sess_id and user.username != user_sess_id:
        return None

    if user.id != token_user_id and user.username != token_user_id:
        # only invalid if the token id isn't the user's id OR username
        # since the username is also unique
        return None

    return access_token
Beispiel #2
0
    def save_session(self, app, session, response):
        domain = self.get_cookie_domain(app)
        token = session.get_updated_token(app)
        if token:
            response.set_cookie(
                app.session_cookie_name, token,
                expires=self.get_expiration_time(app, session),
                httponly=True, domain=domain)
            # try to get user, execption means they're not logged in
            try:
                user = get_current_user()
            except Unauthorized:
                user = None

            if user and not g.access_token:
                _create_access_token_cookie(app, response, user)
        else:
            # If there isn't a session token, we should set
            # the cookies to nothing and expire them immediately.
            #
            # This supports the case where the user logs out partially
            # into their timeout window and the session gets cleared. We
            # also need to clear the cookies in this case.
            #
            # NOTE: The session token will STILL BE VALID until its
            #       expiration it just won't be stored in the cookie
            #       anymore
            response.set_cookie(
                app.session_cookie_name,
                expires=0,
                httponly=True, domain=domain)
            response.set_cookie(
                app.config['ACCESS_TOKEN_COOKIE_NAME'],
                expires=0,
                httponly=True, domain=domain)
Beispiel #3
0
def logout(next_url=None):
    # Call get_current_user (but ignore the result) just to check that either
    # the user is logged in or that authorization is mocked.
    user = get_current_user()
    if not user:
        raise Unauthorized("You are not logged in")
    if flask.session['provider'] == IdentityProvider.itrust:
        next_url = flask.current_app.config['ITRUST_GLOBAL_LOGOUT'] + next_url
    flask.session.clear()
    return next_url
Beispiel #4
0
def authorize(*args, **kwargs):
    """
    OIDC Authorization Endpoint

    From the OIDC Specification:

    3.1.1.  Authorization Code Flow Steps
    The Authorization Code Flow goes through the following steps.

    - Client prepares an Authentication Request containing the desired request
      parameters.
    - Client sends the request to the Authorization Server.
    - Authorization Server Authenticates the End-User.
    - Authorization Server obtains End-User Consent/Authorization.
    - Authorization Server sends the End-User back to the Client with an
      Authorization Code.
    - Client requests a response using the Authorization Code at the Token
      Endpoint.
    - Client receives a response that contains an ID Token and Access Token in
      the response body.
    - Client validates the ID token and retrieves the End-User's Subject
      Identifier.

    Args:
        *args: additional arguments
        **kwargs: additional keyword arguments
    """
    need_authentication = False
    try:
        user = get_current_user()
    except Unauthorized:
        need_authentication = True

    if need_authentication or not user:
        redirect_url = config.get("BASE_URL") + flask.request.full_path
        params = {"redirect": redirect_url}
        login_url = config.get("DEFAULT_LOGIN_URL")
        idp = flask.request.args.get("idp")
        if idp:
            if idp not in IDP_URL_MAP or idp not in config["OPENID_CONNECT"]:
                raise UserError("idp {} is not supported".format(idp))
            idp_url = IDP_URL_MAP[idp]
            login_url = "{}/login/{}".format(config.get("BASE_URL"), idp_url)
        login_url = add_params_to_uri(login_url, params)
        return flask.redirect(login_url)

    try:
        grant = server.validate_consent_request(end_user=user)
    except OAuth2Error as e:
        raise Unauthorized("{} failed to authorize".format(str(e)))

    client_id = grant.client.client_id
    with flask.current_app.db.session as session:
        client = session.query(Client).filter_by(client_id=client_id).first()

    # TODO: any way to get from grant?
    confirm = flask.request.form.get("confirm") or flask.request.args.get(
        "confirm")
    if client.auto_approve:
        confirm = "yes"
    if confirm is not None:
        response = _handle_consent_confirmation(user, confirm)
        # if it's a 302 for POST confirm, return 200 instead and include
        # redirect url in body because browser ajax POST doesn't follow
        # cross origin redirect
        if flask.request.method == "POST" and response.status_code == 302:
            return flask.jsonify({"redirect": response.headers["Location"]})
    else:
        # no confirm param, so no confirmation has occured yet
        response = _authorize(user, grant, client)

    return response
Beispiel #5
0
def _get_auth_response_for_prompts(prompts, grant, user, client, scope):
    """
    Get response based on prompt parameter. TODO: not completely conforming yet

    FIXME: To conform to spec, some of the prompt params should be handled
    before AuthN or if it fails (so adequate and useful errors are provided).

    Right now the behavior is that the endpoint will just continue to
    redirect the user to log in without checking these params....

    Args:
        prompts (TYPE): Description
        grant (TYPE): Description
        user (TYPE): Description
        client (TYPE): Description
        scope (TYPE): Description

    Returns:
        TYPE: Description
    """
    show_consent_screen = True

    if prompts:
        prompts = prompts.split(" ")
        if "none" in prompts:
            # don't auth or consent, error if user not logged in
            show_consent_screen = False

            # if none is here, there shouldn't be others
            if len(prompts) != 1:
                error = InvalidRequestError(state=grant.params.get("state"),
                                            uri=grant.params.get("uri"))
                return _get_authorize_error_response(
                    error, grant.params.get("redirect_uri"))

            try:
                get_current_user()
                response = server.create_authorization_response(user)
            except Unauthorized:
                error = AccessDeniedError(state=grant.params.get("state"),
                                          uri=grant.params.get("uri"))
                return _get_authorize_error_response(
                    error, grant.params.get("redirect_uri"))

        if "login" in prompts:
            show_consent_screen = True
            try:
                # Re-AuthN user (kind of).
                # TODO (RR 2018-03-16): this could also include removing active
                # refresh tokens.
                flask.session.clear()

                # For a POST, return the redirect in JSON instead of headers.
                if flask.request.method == "POST":
                    redirect_response = flask.make_response(
                        flask.jsonify(
                            {"redirect": response.headers["Location"]}))
                else:
                    redirect_response = flask.make_response(
                        flask.redirect(flask.url_for(".authorize")))

                clear_cookies(redirect_response)
                return redirect_response
            except Unauthorized:
                error = AccessDeniedError(state=grant.params.get("state"),
                                          uri=grant.params.get("uri"))
                return _get_authorize_error_response(
                    error, grant.params.get("redirect_uri"))

        if "consent" in prompts:
            # show consent screen (which is default behavior so pass)
            pass

        if "select_account" in prompts:
            # allow user to select one of their accounts, we
            # don't support this at the moment
            pass

    if show_consent_screen:
        shown_scopes = [] if not scope else scope.split(" ")
        if "openid" in shown_scopes:
            shown_scopes.remove("openid")

        enabled_idps = config.get("OPENID_CONNECT", {})
        idp_names = []
        for idp, info in enabled_idps.items():
            # prefer name if its there, then just use the key for the provider
            idp_name = info.get("name") or idp.title()
            idp_names.append(idp_name)

        resource_description = [
            SCOPE_DESCRIPTION[s].format(idp_names=" and ".join(idp_names))
            for s in shown_scopes
        ]

        privacy_policy = config.get("BASE_URL").rstrip("/") + "/privacy-policy"

        response = flask.render_template(
            "oauthorize.html",
            grant=grant,
            user=user,
            client=client,
            app_name=config.get("APP_NAME"),
            resource_description=resource_description,
            privacy_policy=privacy_policy,
        )

    return response
Beispiel #6
0
    def save_session(self, app, session, response):
        domain = self.get_cookie_domain(app)
        token = session.get_updated_token(app)
        if token:
            response.set_cookie(
                app.session_cookie_name,
                token,
                expires=self.get_expiration_time(app, session),
                httponly=True,
                domain=domain,
            )
            # try to get user, exception means they're not logged in
            try:
                user = get_current_user()
            except Unauthorized:
                user = None

            user_sess_id = _get_user_id_from_session(session)

            # user_id == '' in session means no login has occured, which is
            # okay if user is hitting with just an access_token
            if user_sess_id != "" and not user:
                response.set_cookie(
                    config["ACCESS_TOKEN_COOKIE_NAME"],
                    expires=0,
                    httponly=True,
                    domain=domain,
                )
            # check that the current user is the one from the session,
            # clear access token if not
            elif user_sess_id != "" and user.id != user_sess_id:
                response.set_cookie(
                    config["ACCESS_TOKEN_COOKIE_NAME"],
                    expires=0,
                    httponly=True,
                    domain=domain,
                )

            # generate an access token and set in cookie if
            # user is logged in AND one of the following:
            # 1. RENEW_ACCESS_TOKEN_BEFORE_EXPIRATION = true in config
            # 2. current access token has expired (no access_token)
            if user and (config.get("RENEW_ACCESS_TOKEN_BEFORE_EXPIRATION")
                         or not flask.g.access_token):
                _create_access_token_cookie(app, session, response, user)
        else:
            # If there isn't a session token, we should set
            # the cookies to nothing and expire them immediately.
            #
            # This supports the case where the user logs out partially
            # into their timeout window and the session gets cleared. We
            # also need to clear the cookies in this case.
            #
            # NOTE: The session token will STILL BE VALID until its
            #       expiration it just won't be stored in the cookie
            #       anymore
            response.set_cookie(app.session_cookie_name,
                                expires=0,
                                httponly=True,
                                domain=domain)
            response.set_cookie(
                config["ACCESS_TOKEN_COOKIE_NAME"],
                expires=0,
                httponly=True,
                domain=domain,
            )
Beispiel #7
0
def authorize(*args, **kwargs):
    """
    OIDC Authorization Endpoint

    From the OIDC Specification:

    3.1.1.  Authorization Code Flow Steps
    The Authorization Code Flow goes through the following steps.

    - Client prepares an Authentication Request containing the desired request
      parameters.
    - Client sends the request to the Authorization Server.
    - Authorization Server Authenticates the End-User.
    - Authorization Server obtains End-User Consent/Authorization.
    - Authorization Server sends the End-User back to the Client with an
      Authorization Code.
    - Client requests a response using the Authorization Code at the Token
      Endpoint.
    - Client receives a response that contains an ID Token and Access Token in
      the response body.
    - Client validates the ID token and retrieves the End-User's Subject
      Identifier.

    Args:
        *args: additional arguments
        **kwargs: additional keyword arguments
    """
    need_authentication = False
    user = None
    try:
        user = get_current_user()
    except Unauthorized:
        need_authentication = True

    idp = flask.request.args.get("idp")
    fence_idp = flask.request.args.get("fence_idp")
    shib_idp = flask.request.args.get("shib_idp")

    login_url = None
    if not idp:
        if not config.get("DEFAULT_LOGIN_IDP") and "default" not in (
                config.get("ENABLED_IDENTITY_PROVIDERS") or {}):
            # fall back on deprecated DEFAULT_LOGIN_URL
            login_url = config.get("DEFAULT_LOGIN_URL")
        else:
            default_provider_info, _ = get_login_providers_info()
            idp = default_provider_info["idp"]
            # if more than 1 URL is configured, default to the 1st one
            login_url = default_provider_info["urls"][0]["url"]

    if need_authentication or not user:
        redirect_url = config.get("BASE_URL") + flask.request.full_path
        params = {"redirect": redirect_url}

        if not login_url:
            if idp not in config["OPENID_CONNECT"]:
                raise UserError("idp {} is not supported".format(idp))
            idp_endpoint = get_idp_route_name(idp)
            login_url = "{}/login/{}".format(config.get("BASE_URL"),
                                             idp_endpoint)

        # handle valid extra params for fence multi-tenant and shib login
        if idp == "fence" and fence_idp:
            params["idp"] = fence_idp
            if fence_idp == "shibboleth":
                params["shib_idp"] = shib_idp
        elif idp == "shibboleth" and shib_idp:
            params["shib_idp"] = shib_idp

        # store client_id for later use in login endpoint prepare_login_log()
        flask.session["client_id"] = flask.request.args.get("client_id")

        login_url = add_params_to_uri(login_url, params)
        return flask.redirect(login_url)

    try:
        grant = server.validate_consent_request(end_user=user)
    except OAuth2Error as e:
        raise Unauthorized("Failed to authorize: {}".format(str(e)))

    client_id = grant.client.client_id
    with flask.current_app.db.session as session:
        client = session.query(Client).filter_by(client_id=client_id).first()

    # TODO: any way to get from grant?
    confirm = flask.request.form.get("confirm") or flask.request.args.get(
        "confirm")
    if client.auto_approve:
        confirm = "yes"
    if confirm is not None:
        response = _handle_consent_confirmation(user, confirm)
        # if it's a 302 for POST confirm, return 200 instead and include
        # redirect url in body because browser ajax POST doesn't follow
        # cross origin redirect
        if flask.request.method == "POST" and response.status_code == 302:
            response = flask.jsonify(
                {"redirect": response.headers["Location"]})
    else:
        # no confirm param, so no confirmation has occured yet
        response = _authorize(user, grant, client)

    return response
Beispiel #8
0
def authorize(*args, **kwargs):
    """
    OIDC Authorization Endpoint

    From the OIDC Specification:

    3.1.1.  Authorization Code Flow Steps
    The Authorization Code Flow goes through the following steps.

    - Client prepares an Authentication Request containing the desired request
      parameters.
    - Client sends the request to the Authorization Server.
    - Authorization Server Authenticates the End-User.
    - Authorization Server obtains End-User Consent/Authorization.
    - Authorization Server sends the End-User back to the Client with an
      Authorization Code.
    - Client requests a response using the Authorization Code at the Token
      Endpoint.
    - Client receives a response that contains an ID Token and Access Token in
      the response body.
    - Client validates the ID token and retrieves the End-User's Subject
      Identifier.

    Args:
        *args: additional arguments
        **kwargs: additional keyword arguments
    """
    need_authentication = False
    try:
        user = get_current_user()
    except Unauthorized:
        need_authentication = True

    if need_authentication or not user:
        redirect_url = (
            flask.current_app.config.get('BASE_URL')
            + flask.request.full_path
        )
        params = {
            flask.current_app.config.get('DEFAULT_LOGIN_URL_REDIRECT_PARAM'):
            redirect_url
        }
        login_url = add_params_to_uri(
            flask.current_app.config.get('DEFAULT_LOGIN_URL'), params
        )
        return flask.redirect(login_url)

    try:
        grant = server.validate_authorization_request()
    except OAuth2Error as e:
        raise Unauthorized('{} failed to authorize'.format(str(e)))

    client_id = grant.params.get('client_id')

    with flask.current_app.db.session as session:
        client = (
            session
            .query(Client)
            .filter_by(client_id=client_id)
            .first()
        )

    confirm = grant.params.get('confirm')
    if client.auto_approve is True:
        confirm = 'yes'
    if confirm is not None:
        response = _handle_consent_confirmation(user, confirm)
    else:
        # no confirm param, so no confirmation has occured yet
        response = _authorize(user, grant, client)

    return response
Beispiel #9
0
def _get_auth_response_for_prompts(prompts, grant, user, client, scope):
    """
    Get response based on prompt parameter. TODO: not completely conforming yet

    FIXME: To conform to spec, some of the prompt params should be handled
    before AuthN or if it fails (so adequate and useful errors are provided).

    Right now the behavior is that the endpoint will just continue to
    redirect the user to log in without checking these params....

    Args:
        prompts (TYPE): Description
        grant (TYPE): Description
        user (TYPE): Description
        client (TYPE): Description
        scope (TYPE): Description

    Returns:
        TYPE: Description
    """
    show_consent_screen = True

    if prompts:
        prompts = prompts.split(' ')
        if 'none' in prompts:
            # don't auth or consent, error if user not logged in
            show_consent_screen = False

            # if none is here, there shouldn't be others
            if len(prompts) != 1:
                error = InvalidRequestError(
                    state=grant.params.get('state'),
                    uri=grant.params.get('uri')
                )
                return _get_authorize_error_response(
                    error, grant.params.get('redirect_uri'))

            try:
                get_current_user()
                response = server.create_authorization_response(user)
            except Unauthorized:
                error = AccessDeniedError(
                    state=grant.params.get('state'),
                    uri=grant.params.get('uri')
                )
                return _get_authorize_error_response(
                    error, grant.params.get('redirect_uri'))

        if 'login' in prompts:
            show_consent_screen = True
            try:
                # re-AuthN user
                # TODO not sure if this really counts as re-AuthN...
                handle_login(scope)
            except Unauthorized:
                error = AccessDeniedError(
                    state=grant.params.get('state'),
                    uri=grant.params.get('uri')
                )
                return _get_authorize_error_response(
                    error, grant.params.get('redirect_uri'))

        if 'consent' in prompts:
            # show consent screen (which is default behavior so pass)
            pass

        if 'select_account' in prompts:
            # allow user to select one of their accounts, we
            # don't support this at the moment
            pass

    if show_consent_screen:
        shown_scopes = scope.split(' ')
        if 'openid' in shown_scopes:
            shown_scopes.remove('openid')
        resource_description = [
            SCOPE_DESCRIPTION[scope] for scope in shown_scopes]
        response = flask.render_template(
            'oauthorize.html', grant=grant, user=user, client=client,
            app_name=flask.current_app.config.get('APP_NAME'),
            resource_description=resource_description
        )

    return response