Ejemplo n.º 1
0
def test_redirect_target(client, default_user):
    with client.session_transaction() as session:
        auth.bind_redirect_target("/gh/getsentry/zeus", session=session)
        assert (auth.get_redirect_target(
            clear=False, session=session) == "/gh/getsentry/zeus")
        assert (auth.get_redirect_target(
            clear=True, session=session) == "/gh/getsentry/zeus")
        assert auth.get_redirect_target(session=session) is None
Ejemplo n.º 2
0
def test_login_binds_next_target(client):
    resp = client.get("/auth/github?next=/gh/github/zeus")
    assert resp.status_code == 302
    location, querystring = resp.headers["Location"].split("?", 1)
    assert location == GITHUB_AUTH_URI
    qs = parse_qs(querystring)
    assert qs["client_id"] == ["github.client-id"]
    assert qs["redirect_uri"] == ["http://localhost/auth/github/complete"]
    assert qs["response_type"] == ["code"]
    assert sorted(qs["scope"][0].split(",")) == ["read:org", "repo", "user:email"]
    assert auth.get_redirect_target(False) == "/gh/github/zeus"
Ejemplo n.º 3
0
def test_login_complete_with_next_target(client, db_session, mocker, responses):

    responses.add(
        "POST",
        GITHUB_TOKEN_URI,
        json={
            "token_type": "bearer",
            "scope": "user:email,read:org",
            "access_token": "access-token",
        },
    )

    # TOOD(dcramer): ideally we could test the header easily, but responses
    # doesnt make that highly accessible yet
    responses.add(
        "GET",
        "https://api.github.com/user",
        match_querystring=True,
        json={"id": 1, "login": "******", "email": "*****@*****.**"},
    )
    responses.add(responses.GET, "https://api.github.com/user/orgs", json=[])
    responses.add(
        "GET",
        "https://api.github.com/user/emails",
        match_querystring=True,
        json=[
            {"email": "*****@*****.**", "verified": True, "primary": True},
            {"email": "*****@*****.**", "verified": False, "primary": False},
        ],
    )

    with client.session_transaction() as session:
        auth.bind_redirect_target("/gh/getsentry/zeus", session=session)

    resp = client.get("/auth/github/complete?code=abc")

    assert resp.status_code == 302, repr(resp)
    assert resp.headers["Location"] == "http://localhost/gh/getsentry/zeus"

    assert auth.get_redirect_target() is None
Ejemplo n.º 4
0
    def get(self):
        # TODO(dcramer): handle errors
        oauth = get_oauth_session(state=session.pop("oauth_state", None))
        try:
            resp = oauth.fetch_token(
                GITHUB_TOKEN_URI,
                client_secret=current_app.config["GITHUB_CLIENT_SECRET"],
                authorization_response=request.url,
            )
        except OAuth2Error:
            current_app.logger.exception("oauth.error")
            # redirect, as this is likely temporary based on server data
            return redirect(auth.get_redirect_target(clear=True) or "/")

        if resp is None or resp.get("access_token") is None:
            return Response("Access denied: reason=%s error=%s resp=%s" %
                            (request.args["error"],
                             request.args["error_description"], resp))

        assert resp.get("token_type") == "bearer"

        scopes = resp["scope"][0].split(",")
        if "user:email" not in scopes:
            raise NotImplementedError

        # fetch user details
        client = GitHubClient(token=resp["access_token"])
        user_data = client.get("/user")

        identity_config = {
            "access_token": resp["access_token"],
            "refresh_token": resp.get("refresh_token"),
            "login": user_data["login"],
        }

        email_list = client.get("/user/emails")
        email_list.append({
            "email":
            "{}@users.noreply.github.com".format(user_data["login"]),
            "verified":
            True,
        })

        primary_email = user_data.get("email")
        # HACK(dcramer): capture github's anonymous email addresses when they're not listed
        # (we haven't actually confirmed they're not listed)
        if not primary_email:
            primary_email = next((e["email"] for e in email_list
                                  if e["verified"] and e["primary"]))

        try:
            # we first attempt to create a new user + identity
            with db.session.begin_nested():
                user = User(email=primary_email)
                db.session.add(user)
                identity = Identity(
                    user=user,
                    external_id=str(user_data["id"]),
                    provider="github",
                    scopes=scopes,
                    config=identity_config,
                )
                db.session.add(identity)
            user_id = user.id
            new_user = True
        except IntegrityError:
            # if that fails, assume the identity exists
            identity = Identity.query.filter(
                Identity.external_id == str(user_data["id"]),
                Identity.provider == "github",
            ).first()

            # and if it doesnt, attempt to find a matching user,
            # as it means the failure above was due to that
            if not identity:
                user = User.query.filter(User.email == primary_email).first()
                assert (
                    user
                )  # this should not be possible unless we've got a race condition
                identity = Identity(
                    user=user,
                    external_id=str(user_data["id"]),
                    provider="github",
                    scopes=scopes,
                    config=identity_config,
                )
                db.session.add(identity)
                user_id = user.id
            else:
                identity.config = identity_config
                identity.scopes = scopes
                db.session.add(identity)
                user_id = identity.user_id
            new_user = False
        db.session.flush()

        for email in email_list:
            try:
                with db.session.begin_nested():
                    db.session.add(
                        Email(
                            user_id=user_id,
                            email=email["email"],
                            verified=email["verified"],
                        ))
            except IntegrityError:
                pass

        db.session.commit()

        # forcefully expire a session after permanent_session_lifetime
        # Note: this is enforced in zeus.auth
        auth.login_user(user_id)

        user = auth.get_current_user()
        if new_user:
            # update synchronously so the new user has a better experience
            sync_github_access(user_id=user.id)
        else:
            sync_github_access.delay(user_id=user.id)

        next_uri = auth.get_redirect_target(clear=True) or "/"
        if "/login" in next_uri or "/auth/github" in next_uri:
            next_uri = "/"
        return redirect(next_uri)
Ejemplo n.º 5
0
    def get(self):
        redirect_uri = request.url
        flow = get_auth_flow(redirect_uri=redirect_uri)
        try:
            oauth_response = flow.step2_exchange(request.args['code'])
        except FlowExchangeError:
            return redirect('/?auth_error=true')

        scopes = oauth_response.token_response['scope'].split(',')

        if 'user:email' not in scopes:
            raise NotImplementedError

        # fetch user details
        github = GitHubClient(token=oauth_response.access_token)
        user_data = github.get('/user')

        identity_config = {
            'access_token': oauth_response.access_token,
            'refresh_token': oauth_response.refresh_token,
            'login': user_data['login'],
        }

        email_list = github.get('/user/emails')
        email_list.append({
            'email':
            '{}@users.noreply.github.com'.format(user_data['login']),
            'verified':
            True,
        })

        primary_email = user_data.get('email')
        # HACK(dcramer): capture github's anonymous email addresses when they're not listed
        # (we haven't actually confirmed they're not listed)
        if not primary_email:
            primary_email = next((e['email'] for e in email_list
                                  if e['verified'] and e['primary']))

        try:
            # we first attempt to create a new user + identity
            with db.session.begin_nested():
                user = User(email=primary_email, )
                db.session.add(user)
                identity = Identity(
                    user=user,
                    external_id=str(user_data['id']),
                    provider='github',
                    scopes=scopes,
                    config=identity_config,
                )
                db.session.add(identity)
            user_id = user.id
        except IntegrityError:
            # if that fails, assume the identity exists
            identity = Identity.query.filter(
                Identity.external_id == str(user_data['id']),
                Identity.provider == 'github',
            ).first()

            # and if it doesnt, attempt to find a matching user,
            # as it means the failure above was due to that
            if not identity:
                user = User.query.filter(User.email == primary_email).first()
                assert user  # this should not be possible unless we've got a race condition
                identity = Identity(
                    user=user,
                    external_id=str(user_data['id']),
                    provider='github',
                    scopes=scopes,
                    config=identity_config,
                )
                db.session.add(identity)
                user_id = user.id
            else:
                identity.config = identity_config
                identity.scopes = scopes
                db.session.add(identity)
                user_id = identity.user_id

        db.session.flush()

        for email in email_list:
            try:
                with db.session.begin_nested():
                    db.session.add(
                        Email(
                            user_id=user_id,
                            email=email['email'],
                            verified=email['verified'],
                        ))
            except IntegrityError:
                pass

        db.session.commit()

        # forcefully expire a session after permanent_session_lifetime
        # Note: this is enforced in zeus.auth
        auth.login_user(user_id)

        # now lets try to update the repos they have access to based on whats
        # enabled
        user = auth.get_current_user()
        grant_access_to_existing_repos(user)

        return redirect(auth.get_redirect_target(clear=True) or '/')