Example #1
0
async def test_invalid_username(factory: ComponentFactory) -> None:
    user_info = TokenUserInfo(
        username="******",
        name="Example Person",
        uid=4137,
        groups=[TokenGroup(name="foo", id=1000)],
    )
    token_service = factory.create_token_service()
    async with factory.session.begin():
        session_token = await token_service.create_session_token(
            user_info,
            scopes=["read:all", "admin:token"],
            ip_address="127.0.0.1",
        )
    data = await token_service.get_data(session_token)
    assert data

    # Cannot create any type of token with an invalid name.
    for user in (
            "<bootstrap>",
            "<internal>",
            "in+valid",
            " invalid",
            "invalid ",
            "in/valid",
            "in@valid",
            "-invalid",
            "invalid-",
            "in--valid",
    ):
        user_info.username = user
        with pytest.raises(PermissionDeniedError):
            await token_service.create_session_token(user_info,
                                                     scopes=[],
                                                     ip_address="127.0.0.1")
        data.username = user
        with pytest.raises(PermissionDeniedError):
            await token_service.create_user_token(data,
                                                  user,
                                                  token_name="n",
                                                  scopes=[],
                                                  ip_address="127.0.0.1")
        with pytest.raises(PermissionDeniedError):
            await token_service.get_notebook_token(data,
                                                   ip_address="127.0.0.1")
        with pytest.raises(PermissionDeniedError):
            await token_service.get_internal_token(data,
                                                   service="s",
                                                   scopes=[],
                                                   ip_address="127.0.0.1")
        with pytest.raises(ValidationError):
            AdminTokenRequest(username=user, token_type=TokenType.service)
        request = AdminTokenRequest(username="******",
                                    token_type=TokenType.service)
        request.username = user
        with pytest.raises(PermissionDeniedError):
            await token_service.create_token_from_admin_request(
                request, data, ip_address="127.0.0.1")
Example #2
0
async def test_list(factory: ComponentFactory) -> None:
    user_info = TokenUserInfo(username="******",
                              name="Example Person",
                              uid=4137)
    token_service = factory.create_token_service()
    async with factory.session.begin():
        session_token = await token_service.create_session_token(
            user_info, scopes=["user:token"], ip_address="127.0.0.1")
    data = await token_service.get_data(session_token)
    assert data
    async with factory.session.begin():
        user_token = await token_service.create_user_token(
            data,
            data.username,
            token_name="some-token",
            scopes=[],
            ip_address="127.0.0.1",
        )
        other_user_info = TokenUserInfo(username="******",
                                        name="Other Person",
                                        uid=1313)
        other_session_token = await token_service.create_session_token(
            other_user_info, scopes=["admin:token"], ip_address="1.1.1.1")
    admin_data = await token_service.get_data(other_session_token)
    assert admin_data

    async with factory.session.begin():
        session_info = await token_service.get_token_info_unchecked(
            session_token.key)
        assert session_info
        user_token_info = await token_service.get_token_info_unchecked(
            user_token.key)
        assert user_token_info
        other_session_info = await token_service.get_token_info_unchecked(
            other_session_token.key)
        assert other_session_info
        assert await token_service.list_tokens(data, "example") == sorted(
            sorted((session_info, user_token_info), key=lambda t: t.token),
            key=lambda t: t.created,
            reverse=True,
        )
        assert await token_service.list_tokens(admin_data) == sorted(
            sorted(
                (session_info, other_session_info, user_token_info),
                key=lambda t: t.token,
            ),
            key=lambda t: t.created,
            reverse=True,
        )

    # Regular users can't retrieve all tokens.
    with pytest.raises(PermissionDeniedError):
        await token_service.list_tokens(data)
Example #3
0
async def add_expired_session_token(
    user_info: TokenUserInfo,
    *,
    scopes: List[str],
    ip_address: str,
    session: AsyncSession,
) -> None:
    """Add an expired session token to the database.

    This requires going beneath the service layer, since the service layer
    rejects creation of expired tokens (since apart from testing this isn't a
    sensible thing to want to do).

    This does not add the token to Redis, since Redis will refuse to add it
    with a negative expiration time, so can only be used for tests that
    exclusively use the database.

    Parameters
    ----------
    user_info : `gafaelfawr.models.token.TokenUserInfo`
        The user information to associate with the token.
    scopes : List[`str`]
        The scopes of the token.
    ip_address : `str`
        The IP address from which the request came.
    session : `sqlalchemy.ext.asyncio.AsyncSession`
        The database session.
    """
    token_db_store = TokenDatabaseStore(session)
    token_change_store = TokenChangeHistoryStore(session)

    token = Token()
    created = current_datetime()
    expires = created - timedelta(minutes=10)
    data = TokenData(
        token=token,
        token_type=TokenType.session,
        scopes=scopes,
        created=created,
        expires=expires,
        **user_info.dict(),
    )
    history_entry = TokenChangeHistoryEntry(
        token=token.key,
        username=data.username,
        token_type=TokenType.session,
        scopes=scopes,
        expires=expires,
        actor=data.username,
        action=TokenChange.create,
        ip_address=ip_address,
        event_time=created,
    )

    await token_db_store.add(data)
    await token_change_store.add(history_entry)
Example #4
0
    async def create_session_token(self, user_info: TokenUserInfo, *,
                                   scopes: List[str],
                                   ip_address: str) -> Token:
        """Create a new session token.

        Parameters
        ----------
        user_info : `gafaelfawr.models.token.TokenUserInfo`
            The user information to associate with the token.
        scopes : List[`str`]
            The scopes of the token.
        ip_address : `str`
            The IP address from which the request came.

        Returns
        -------
        token : `gafaelfawr.models.token.Token`
            The newly-created token.

        Raises
        ------
        gafaelfawr.exceptions.PermissionDeniedError
            If the provided username is invalid.
        """
        self._validate_username(user_info.username)
        scopes = sorted(scopes)

        token = Token()
        created = current_datetime()
        expires = created + self._config.token_lifetime
        data = TokenData(
            token=token,
            token_type=TokenType.session,
            scopes=scopes,
            created=created,
            expires=expires,
            **user_info.dict(),
        )
        history_entry = TokenChangeHistoryEntry(
            token=token.key,
            username=data.username,
            token_type=TokenType.session,
            scopes=scopes,
            expires=expires,
            actor=data.username,
            action=TokenChange.create,
            ip_address=ip_address,
            event_time=created,
        )

        await self._token_redis_store.store_data(data)
        with self._transaction_manager.transaction():
            self._token_db_store.add(data)
            self._token_change_store.add(history_entry)

        return token
Example #5
0
 async def get_user_info(self, token: Token) -> Optional[TokenUserInfo]:
     """Get user information associated with a token."""
     data = await self.get_data(token)
     if not data:
         return None
     return TokenUserInfo(
         username=data.username,
         name=data.name,
         uid=data.uid,
         email=data.email,
         groups=data.groups,
     )
Example #6
0
async def create_session_token(
    factory: ComponentFactory,
    *,
    username: Optional[str] = None,
    group_names: Optional[List[str]] = None,
    scopes: Optional[List[str]] = None,
) -> TokenData:
    """Create a session token.

    Parameters
    ----------
    factory : `gafaelfawr.factory.ComponentFactory`
        Factory used to create services to add the token.
    username : `str`, optional
        Override the username of the generated token.
    group_namess : List[`str`], optional
        Group memberships the generated token should have.
    scopes : List[`str`], optional
        Scope for the generated token.

    Returns
    -------
    data : `gafaelfawr.models.token.TokenData`
        The data for the generated token.
    """
    if not username:
        username = "******"
    if group_names:
        groups = [TokenGroup(name=g, id=1000) for g in group_names]
    else:
        groups = []
    user_info = TokenUserInfo(
        username=username,
        name="Some User",
        email="*****@*****.**",
        uid=1000,
        groups=groups,
    )
    if not scopes:
        scopes = ["user:token"]
    token_service = factory.create_token_service()
    async with factory.session.begin():
        token = await token_service.create_session_token(
            user_info, scopes=scopes, ip_address="127.0.0.1"
        )
    data = await token_service.get_data(token)
    assert data
    return data
Example #7
0
    async def create_user_info(self, code: str, state: str) -> TokenUserInfo:
        """Given the code from an authentication, create the user information.

        Parameters
        ----------
        code : `str`
            Code returned by a successful authentication.
        state : `str`
            The same random string used for the redirect URL.

        Returns
        -------
        user_info : `gafaelfawr.models.token.TokenUserInfo`
            The user information corresponding to that authentication.

        Raises
        ------
        httpx.HTTPError
            An HTTP client error occurred trying to talk to the authentication
            provider.
        gafaelfawr.exceptions.GitHubException
            GitHub responded with an error to a request.
        """
        self._logger.info("Getting user information from GitHub")
        github_token = await self._get_access_token(code, state)
        user_info = await self._get_user_info(github_token)

        groups = []
        invalid_groups = {}
        for team in user_info.teams:
            try:
                groups.append(TokenGroup(name=team.group_name, id=team.gid))
            except ValidationError as e:
                invalid_groups[team.group_name] = str(e)
        if invalid_groups:
            self._logger.warning("Ignoring invalid groups",
                                 invalid_groups=invalid_groups)
        return TokenUserInfo(
            username=user_info.username.lower(),
            name=user_info.name,
            email=user_info.email,
            uid=user_info.uid,
            groups=groups,
        )
Example #8
0
async def test_success_minimal(setup: SetupTest) -> None:
    user_info = TokenUserInfo(username="******", uid=1234)
    token_service = setup.factory.create_token_service()
    token = await token_service.create_session_token(user_info,
                                                     scopes=["read:all"],
                                                     ip_address="127.0.0.1")

    r = await setup.client.get(
        "/auth",
        params={"scope": "read:all"},
        headers={"Authorization": f"Bearer {token}"},
    )
    assert r.status_code == 200
    assert r.headers["X-Auth-Request-Token-Scopes"] == "read:all"
    assert r.headers["X-Auth-Request-Scopes-Accepted"] == "read:all"
    assert r.headers["X-Auth-Request-Scopes-Satisfy"] == "all"
    assert r.headers["X-Auth-Request-User"] == "user"
    assert r.headers["X-Auth-Request-Uid"] == "1234"
    assert "X-Auth-Request-Name" not in r.headers
    assert "X-Auth-Request-Email" not in r.headers
    assert "X-Auth-Request-Groups" not in r.headers
Example #9
0
async def _selenium_startup(token_path: str) -> None:
    """Startup hook for the app run in Selenium testing mode."""
    config = await config_dependency()
    user_info = TokenUserInfo(username="******", name="Test User", uid=1000)
    scopes = list(config.known_scopes.keys())

    async with ComponentFactory.standalone() as factory:
        async with factory.session.begin():
            # Add an expired token so that we can test display of expired
            # tokens.
            await add_expired_session_token(
                user_info,
                scopes=scopes,
                ip_address="127.0.0.1",
                session=factory.session,
            )

            # Add the valid session token.
            token_service = factory.create_token_service()
            token = await token_service.create_session_token(
                user_info, scopes=scopes, ip_address="127.0.0.1")

    with open(token_path, "w") as f:
        f.write(str(token))
Example #10
0
async def test_token_info(setup: SetupTest) -> None:
    user_info = TokenUserInfo(
        username="******",
        name="Example Person",
        email="*****@*****.**",
        uid=45613,
        groups=[TokenGroup(name="foo", id=12313)],
    )
    token_service = setup.factory.create_token_service()
    session_token = await token_service.create_session_token(
        user_info, scopes=["exec:admin", "user:token"], ip_address="127.0.0.1")

    r = await setup.client.get(
        "/auth/api/v1/token-info",
        headers={"Authorization": f"bearer {session_token}"},
    )
    assert r.status_code == 200
    data = r.json()
    assert data == {
        "token": session_token.key,
        "username": "******",
        "token_type": "session",
        "scopes": ["exec:admin", "user:token"],
        "created": ANY,
        "expires": ANY,
    }
    now = datetime.now(tz=timezone.utc)
    created = datetime.fromtimestamp(data["created"], tz=timezone.utc)
    assert now - timedelta(seconds=2) <= created <= now
    expires = created + timedelta(minutes=setup.config.issuer.exp_minutes)
    assert datetime.fromtimestamp(data["expires"], tz=timezone.utc) == expires

    r = await setup.client.get(
        "/auth/api/v1/user-info",
        headers={"Authorization": f"bearer {session_token}"},
    )
    assert r.status_code == 200
    session_user_info = r.json()
    assert session_user_info == {
        "username": "******",
        "name": "Example Person",
        "email": "*****@*****.**",
        "uid": 45613,
        "groups": [{
            "name": "foo",
            "id": 12313,
        }],
    }

    # Check the same with a user token, which has some additional associated
    # data.
    expires = now + timedelta(days=100)
    data = await token_service.get_data(session_token)
    user_token = await token_service.create_user_token(
        data,
        data.username,
        token_name="some-token",
        scopes=["exec:admin"],
        expires=expires,
        ip_address="127.0.0.1",
    )

    r = await setup.client.get(
        "/auth/api/v1/token-info",
        headers={"Authorization": f"bearer {user_token}"},
    )
    assert r.status_code == 200
    data = r.json()
    assert data == {
        "token": user_token.key,
        "username": "******",
        "token_type": "user",
        "token_name": "some-token",
        "scopes": ["exec:admin"],
        "created": ANY,
        "expires": int(expires.timestamp()),
    }

    r = await setup.client.get(
        "/auth/api/v1/user-info",
        headers={"Authorization": f"bearer {user_token}"},
    )
    assert r.status_code == 200
    assert r.json() == session_user_info

    # Test getting a list of tokens for a user.
    state = State(token=session_token)
    r = await setup.client.get(
        "/auth/api/v1/users/example/tokens",
        cookies={COOKIE_NAME: state.as_cookie()},
    )
Example #11
0
async def test_create_delete_modify(setup: SetupTest,
                                    caplog: LogCaptureFixture) -> None:
    user_info = TokenUserInfo(
        username="******",
        name="Example Person",
        email="*****@*****.**",
        uid=45613,
        groups=[TokenGroup(name="foo", id=12313)],
    )
    token_service = setup.factory.create_token_service()
    session_token = await token_service.create_session_token(
        user_info,
        scopes=["read:all", "exec:admin", "user:token"],
        ip_address="127.0.0.1",
    )
    csrf = await setup.login(session_token)

    expires = current_datetime() + timedelta(days=100)
    r = await setup.client.post(
        "/auth/api/v1/users/example/tokens",
        headers={"X-CSRF-Token": csrf},
        json={
            "token_name": "some token",
            "scopes": ["read:all"],
            "expires": int(expires.timestamp()),
        },
    )
    assert r.status_code == 201
    assert r.json() == {"token": ANY}
    user_token = Token.from_str(r.json()["token"])
    token_url = r.headers["Location"]
    assert token_url == f"/auth/api/v1/users/example/tokens/{user_token.key}"

    r = await setup.client.get(token_url)
    assert r.status_code == 200
    info = r.json()
    assert info == {
        "token": user_token.key,
        "username": "******",
        "token_name": "some token",
        "token_type": "user",
        "scopes": ["read:all"],
        "created": ANY,
        "expires": int(expires.timestamp()),
    }

    # Check that this is the same information as is returned by the token-info
    # route.  This is a bit tricky to do since the cookie will take precedence
    # over the Authorization header, but we can't just delete the cookie since
    # we'll lose the CSRF token.  Save the cookie and delete it, and then
    # later restore it.
    cookie = setup.client.cookies.pop(COOKIE_NAME)
    r = await setup.client.get(
        "/auth/api/v1/token-info",
        headers={"Authorization": f"bearer {user_token}"},
    )
    assert r.status_code == 200
    assert r.json() == info
    setup.client.cookies.set(COOKIE_NAME, cookie, domain=TEST_HOSTNAME)

    # Listing all tokens for this user should return the user token and a
    # session token.
    r = await setup.client.get("/auth/api/v1/users/example/tokens")
    assert r.status_code == 200
    assert r.json() == sorted(
        [
            {
                "token": session_token.key,
                "username": "******",
                "token_type": "session",
                "scopes": ["exec:admin", "read:all", "user:token"],
                "created": ANY,
                "expires": ANY,
            },
            info,
        ],
        key=lambda t: t["token"],
    )

    # Change the name, scope, and expiration of the token.
    caplog.clear()
    new_expires = current_datetime() + timedelta(days=200)
    r = await setup.client.patch(
        token_url,
        headers={"X-CSRF-Token": csrf},
        json={
            "token_name": "happy token",
            "scopes": ["exec:admin"],
            "expires": int(new_expires.timestamp()),
        },
    )
    assert r.status_code == 201
    assert r.json() == {
        "token": user_token.key,
        "username": "******",
        "token_name": "happy token",
        "token_type": "user",
        "scopes": ["exec:admin"],
        "created": ANY,
        "expires": int(new_expires.timestamp()),
    }

    # Check the logging.  Regression test for a bug where new expirations
    # would be logged as raw datetime objects instead of timestamps.
    log = json.loads(caplog.record_tuples[0][2])
    assert log == {
        "expires": int(new_expires.timestamp()),
        "event": "Modified token",
        "key": user_token.key,
        "level": "info",
        "logger": "gafaelfawr",
        "method": "PATCH",
        "path": token_url,
        "remote": "127.0.0.1",
        "request_id": ANY,
        "scope": "exec:admin read:all user:token",
        "token": session_token.key,
        "token_name": "happy token",
        "token_scope": "exec:admin",
        "token_source": "cookie",
        "user": "******",
        "user_agent": ANY,
    }

    # Delete the token.
    r = await setup.client.delete(token_url, headers={"X-CSRF-Token": csrf})
    assert r.status_code == 204
    r = await setup.client.get(token_url)
    assert r.status_code == 404

    # Deleting again should return 404.
    r = await setup.client.delete(token_url, headers={"X-CSRF-Token": csrf})
    assert r.status_code == 404

    # This user should now have only one token.
    r = await setup.client.get("/auth/api/v1/users/example/tokens")
    assert r.status_code == 200
    assert len(r.json()) == 1

    # We should be able to see the change history for the token.
    r = await setup.client.get(token_url + "/change-history")
    assert r.status_code == 200
    assert r.json() == [
        {
            "token": user_token.key,
            "username": "******",
            "token_type": "user",
            "token_name": "happy token",
            "scopes": ["exec:admin"],
            "expires": int(new_expires.timestamp()),
            "actor": "example",
            "action": "revoke",
            "ip_address": "127.0.0.1",
            "event_time": ANY,
        },
        {
            "token": user_token.key,
            "username": "******",
            "token_type": "user",
            "token_name": "happy token",
            "scopes": ["exec:admin"],
            "expires": int(new_expires.timestamp()),
            "actor": "example",
            "action": "edit",
            "old_token_name": "some token",
            "old_scopes": ["read:all"],
            "old_expires": int(expires.timestamp()),
            "ip_address": "127.0.0.1",
            "event_time": ANY,
        },
        {
            "token": user_token.key,
            "username": "******",
            "token_type": "user",
            "token_name": "some token",
            "scopes": ["read:all"],
            "expires": int(expires.timestamp()),
            "actor": "example",
            "action": "create",
            "ip_address": "127.0.0.1",
            "event_time": ANY,
        },
    ]
Example #12
0
async def test_internal_token(setup: SetupTest) -> None:
    user_info = TokenUserInfo(
        username="******",
        name="Example Person",
        uid=4137,
        groups=[TokenGroup(name="foo", id=1000)],
    )
    token_service = setup.factory.create_token_service()
    session_token = await token_service.create_session_token(
        user_info,
        scopes=["read:all", "exec:admin", "user:token"],
        ip_address="127.0.0.1",
    )
    data = await token_service.get_data(session_token)
    assert data

    internal_token = await token_service.get_internal_token(
        data,
        service="some-service",
        scopes=["read:all"],
        ip_address="2001:db8::45",
    )
    assert await token_service.get_user_info(internal_token) == user_info
    info = token_service.get_token_info_unchecked(internal_token.key)
    assert info and info == TokenInfo(
        token=internal_token.key,
        username=user_info.username,
        token_type=TokenType.internal,
        service="some-service",
        scopes=["read:all"],
        created=info.created,
        last_used=None,
        expires=data.expires,
        parent=session_token.key,
    )
    assert_is_now(info.created)

    # Cannot request a scope that the parent token doesn't have.
    with pytest.raises(InvalidScopesError):
        await token_service.get_internal_token(
            data,
            service="some-service",
            scopes=["read:some"],
            ip_address="127.0.0.1",
        )

    # Creating another internal token from the same parent token with the same
    # parameters just returns the same internal token as before.
    new_internal_token = await token_service.get_internal_token(
        data,
        service="some-service",
        scopes=["read:all"],
        ip_address="127.0.0.1",
    )
    assert internal_token == new_internal_token

    history = token_service.get_change_history(
        data, token=internal_token.key, username=data.username
    )
    assert history.entries == [
        TokenChangeHistoryEntry(
            token=internal_token.key,
            username=data.username,
            token_type=TokenType.internal,
            parent=data.token.key,
            service="some-service",
            scopes=["read:all"],
            expires=data.expires,
            actor=data.username,
            action=TokenChange.create,
            ip_address="2001:db8::45",
            event_time=info.created,
        )
    ]

    # A different scope or a different service results in a new token.
    new_internal_token = await token_service.get_internal_token(
        data,
        service="some-service",
        scopes=["exec:admin"],
        ip_address="127.0.0.1",
    )
    assert internal_token != new_internal_token
    new_internal_token = await token_service.get_internal_token(
        data,
        service="another-service",
        scopes=["read:all"],
        ip_address="127.0.0.1",
    )
    assert internal_token != new_internal_token

    # Check that the expiration time is capped by creating a user token that
    # doesn't expire and then creating a notebook token from it.  Use this to
    # test a token with empty scopes.
    user_token = await token_service.create_user_token(
        data,
        data.username,
        token_name="some token",
        scopes=["exec:admin"],
        expires=None,
        ip_address="127.0.0.1",
    )
    data = await token_service.get_data(user_token)
    assert data
    new_internal_token = await token_service.get_internal_token(
        data, service="some-service", scopes=[], ip_address="127.0.0.1"
    )
    assert new_internal_token != internal_token
    info = token_service.get_token_info_unchecked(new_internal_token.key)
    assert info and info.scopes == []
    expires = info.created + timedelta(minutes=setup.config.issuer.exp_minutes)
    assert info.expires == expires
Example #13
0
async def test_create_delete_modify(
    client: AsyncClient, factory: ComponentFactory, caplog: LogCaptureFixture
) -> None:
    user_info = TokenUserInfo(
        username="******",
        name="Example Person",
        email="*****@*****.**",
        uid=45613,
        groups=[TokenGroup(name="foo", id=12313)],
    )
    token_service = factory.create_token_service()
    async with factory.session.begin():
        session_token = await token_service.create_session_token(
            user_info,
            scopes=["read:all", "exec:admin", "user:token"],
            ip_address="127.0.0.1",
        )
    csrf = await set_session_cookie(client, session_token)

    expires = current_datetime() + timedelta(days=100)
    r = await client.post(
        "/auth/api/v1/users/example/tokens",
        headers={"X-CSRF-Token": csrf},
        json={
            "token_name": "some token",
            "scopes": ["read:all"],
            "expires": int(expires.timestamp()),
        },
    )
    assert r.status_code == 201
    assert r.json() == {"token": ANY}
    user_token = Token.from_str(r.json()["token"])
    token_url = r.headers["Location"]
    assert token_url == f"/auth/api/v1/users/example/tokens/{user_token.key}"

    r = await client.get(token_url)
    assert r.status_code == 200
    info = r.json()
    assert info == {
        "token": user_token.key,
        "username": "******",
        "token_name": "some token",
        "token_type": "user",
        "scopes": ["read:all"],
        "created": ANY,
        "expires": int(expires.timestamp()),
    }

    # Check that this is the same information as is returned by the token-info
    # route.  This is a bit tricky to do since the cookie will take precedence
    # over the Authorization header, but we can't just delete the cookie since
    # we'll lose the CSRF token.  Save the cookie and delete it, and then
    # later restore it.
    cookie = client.cookies.pop(COOKIE_NAME)
    r = await client.get(
        "/auth/api/v1/token-info",
        headers={"Authorization": f"bearer {user_token}"},
    )
    assert r.status_code == 200
    assert r.json() == info
    client.cookies.set(COOKIE_NAME, cookie, domain=TEST_HOSTNAME)

    # Listing all tokens for this user should return the user token and a
    # session token.
    r = await client.get("/auth/api/v1/users/example/tokens")
    assert r.status_code == 200
    data = r.json()

    # Adjust for sorting, which will be by creation date and then token.
    assert len(data) == 2
    if data[0] == info:
        session_info = data[1]
    else:
        assert data[1] == info
        session_info = data[0]
    assert session_info == {
        "token": session_token.key,
        "username": "******",
        "token_type": "session",
        "scopes": ["exec:admin", "read:all", "user:token"],
        "created": ANY,
        "expires": ANY,
    }

    # Change the name, scope, and expiration of the token.
    caplog.clear()
    new_expires = current_datetime() + timedelta(days=200)
    r = await client.patch(
        token_url,
        headers={"X-CSRF-Token": csrf},
        json={
            "token_name": "happy token",
            "scopes": ["exec:admin"],
            "expires": int(new_expires.timestamp()),
        },
    )
    assert r.status_code == 201
    assert r.json() == {
        "token": user_token.key,
        "username": "******",
        "token_name": "happy token",
        "token_type": "user",
        "scopes": ["exec:admin"],
        "created": ANY,
        "expires": int(new_expires.timestamp()),
    }

    # Check the logging.  Regression test for a bug where new expirations
    # would be logged as raw datetime objects instead of timestamps.
    assert parse_log(caplog) == [
        {
            "expires": int(new_expires.timestamp()),
            "event": "Modified token",
            "httpRequest": {
                "requestMethod": "PATCH",
                "requestUrl": f"https://{TEST_HOSTNAME}{token_url}",
                "remoteIp": "127.0.0.1",
            },
            "key": user_token.key,
            "scope": "exec:admin read:all user:token",
            "severity": "info",
            "token": session_token.key,
            "token_name": "happy token",
            "token_scope": "exec:admin",
            "token_source": "cookie",
            "user": "******",
        }
    ]

    # Delete the token.
    r = await client.delete(token_url, headers={"X-CSRF-Token": csrf})
    assert r.status_code == 204
    r = await client.get(token_url)
    assert r.status_code == 404

    # Deleting again should return 404.
    r = await client.delete(token_url, headers={"X-CSRF-Token": csrf})
    assert r.status_code == 404

    # This user should now have only one token.
    r = await client.get("/auth/api/v1/users/example/tokens")
    assert r.status_code == 200
    assert len(r.json()) == 1

    # We should be able to see the change history for the token.
    r = await client.get(token_url + "/change-history")
    assert r.status_code == 200
    assert r.json() == [
        {
            "token": user_token.key,
            "username": "******",
            "token_type": "user",
            "token_name": "happy token",
            "scopes": ["exec:admin"],
            "expires": int(new_expires.timestamp()),
            "actor": "example",
            "action": "revoke",
            "ip_address": "127.0.0.1",
            "event_time": ANY,
        },
        {
            "token": user_token.key,
            "username": "******",
            "token_type": "user",
            "token_name": "happy token",
            "scopes": ["exec:admin"],
            "expires": int(new_expires.timestamp()),
            "actor": "example",
            "action": "edit",
            "old_token_name": "some token",
            "old_scopes": ["read:all"],
            "old_expires": int(expires.timestamp()),
            "ip_address": "127.0.0.1",
            "event_time": ANY,
        },
        {
            "token": user_token.key,
            "username": "******",
            "token_type": "user",
            "token_name": "some token",
            "scopes": ["read:all"],
            "expires": int(expires.timestamp()),
            "actor": "example",
            "action": "create",
            "ip_address": "127.0.0.1",
            "event_time": ANY,
        },
    ]
Example #14
0
    async def create_user_info(self, code: str, state: str) -> TokenUserInfo:
        """Given the code from a successful authentication, get a token.

        Parameters
        ----------
        code : `str`
            Code returned by a successful authentication.
        state : `str`
            The same random string used for the redirect URL.

        Returns
        -------
        user_info : `gafaelfawr.models.token.TokenUserInfo`
            The user information corresponding to that authentication.

        Raises
        ------
        gafaelfawr.exceptions.OIDCException
            The OpenID Connect provider responded with an error to a request.
        httpx.HTTPError
            An HTTP client error occurred trying to talk to the authentication
            provider.
        jwt.exceptions.InvalidTokenError
            The token returned by the OpenID Connect provider was invalid.
        """
        data = {
            "grant_type": "authorization_code",
            "client_id": self._config.client_id,
            "client_secret": self._config.client_secret,
            "code": code,
            "redirect_uri": self._config.redirect_url,
        }
        self._logger.info("Retrieving ID token from %s",
                          self._config.token_url)
        r = await self._http_client.post(
            self._config.token_url,
            data=data,
            headers={"Accept": "application/json"},
        )

        # If the call failed, try to extract an error from the reply.  If that
        # fails, just raise an exception for the HTTP status.
        try:
            result = r.json()
        except Exception:
            if r.status_code != 200:
                r.raise_for_status()
            else:
                msg = "Response from {self._config.token_url} not valid JSON"
                raise OIDCException(msg)
        if r.status_code != 200 and "error" in result:
            msg = result["error"] + ": " + result["error_description"]
            raise OIDCException(msg)
        elif r.status_code != 200:
            r.raise_for_status()
        if "id_token" not in result:
            msg = f"No id_token in token reply from {self._config.token_url}"
            raise OIDCException(msg)

        # Extract and verify the token.
        unverified_token = OIDCToken(encoded=result["id_token"])
        try:
            token = await self._verifier.verify_oidc_token(unverified_token)
        except (jwt.InvalidTokenError, VerifyTokenException) as e:
            msg = f"OpenID Connect token verification failed: {str(e)}"
            raise OIDCException(msg)

        # Extract information from it to create the user information.
        groups = []
        invalid_groups = {}
        try:
            for oidc_group in token.claims.get("isMemberOf", []):
                if "name" not in oidc_group:
                    continue
                name = oidc_group["name"]
                if "id" not in oidc_group:
                    invalid_groups[name] = "missing id"
                    continue
                gid = int(oidc_group["id"])
                try:
                    groups.append(TokenGroup(name=name, id=gid))
                except ValidationError as e:
                    invalid_groups[name] = str(e)
        except Exception as e:
            msg = f"isMemberOf claim is invalid: {str(e)}"
            raise OIDCException(msg)
        return TokenUserInfo(
            username=token.username,
            name=token.claims.get("name"),
            email=token.claims.get("email"),
            uid=token.uid,
            groups=groups,
        )
Example #15
0
async def test_user_token(factory: ComponentFactory) -> None:
    user_info = TokenUserInfo(username="******",
                              name="Example Person",
                              uid=4137)
    token_service = factory.create_token_service()
    async with factory.session.begin():
        session_token = await token_service.create_session_token(
            user_info,
            scopes=["read:all", "exec:admin", "user:token"],
            ip_address="127.0.0.1",
        )
    data = await token_service.get_data(session_token)
    assert data
    expires = current_datetime() + timedelta(days=2)

    # Scopes are provided not in sorted order to ensure they're sorted when
    # creating the token.
    async with factory.session.begin():
        user_token = await token_service.create_user_token(
            data,
            "example",
            token_name="some-token",
            scopes=["read:all", "exec:admin"],
            expires=expires,
            ip_address="192.168.0.1",
        )
        assert await token_service.get_user_info(user_token) == user_info
        info = await token_service.get_token_info_unchecked(user_token.key)
    assert info and info == TokenInfo(
        token=user_token.key,
        username=user_info.username,
        token_name="some-token",
        token_type=TokenType.user,
        scopes=["exec:admin", "read:all"],
        created=info.created,
        last_used=None,
        expires=int(expires.timestamp()),
        parent=None,
    )
    assert_is_now(info.created)
    assert await token_service.get_data(user_token) == TokenData(
        token=user_token,
        username=user_info.username,
        token_type=TokenType.user,
        scopes=["exec:admin", "read:all"],
        created=info.created,
        expires=info.expires,
        name=user_info.name,
        uid=user_info.uid,
    )

    async with factory.session.begin():
        history = await token_service.get_change_history(
            data, token=user_token.key, username=data.username)
    assert history.entries == [
        TokenChangeHistoryEntry(
            token=user_token.key,
            username=data.username,
            token_type=TokenType.user,
            token_name="some-token",
            scopes=["exec:admin", "read:all"],
            expires=info.expires,
            actor=data.username,
            action=TokenChange.create,
            ip_address="192.168.0.1",
            event_time=info.created,
        )
    ]
Example #16
0
async def test_internal_token(config: Config,
                              factory: ComponentFactory) -> None:
    user_info = TokenUserInfo(
        username="******",
        name="Example Person",
        uid=4137,
        groups=[TokenGroup(name="foo", id=1000)],
    )
    token_service = factory.create_token_service()
    async with factory.session.begin():
        session_token = await token_service.create_session_token(
            user_info,
            scopes=["read:all", "exec:admin", "user:token"],
            ip_address="127.0.0.1",
        )
    data = await token_service.get_data(session_token)
    assert data

    async with factory.session.begin():
        internal_token = await token_service.get_internal_token(
            data,
            service="some-service",
            scopes=["read:all"],
            ip_address="2001:db8::45",
        )
        assert await token_service.get_user_info(internal_token) == user_info
        info = await token_service.get_token_info_unchecked(internal_token.key)
    assert info and info == TokenInfo(
        token=internal_token.key,
        username=user_info.username,
        token_type=TokenType.internal,
        service="some-service",
        scopes=["read:all"],
        created=info.created,
        last_used=None,
        expires=data.expires,
        parent=session_token.key,
    )
    assert_is_now(info.created)

    # Cannot request a scope that the parent token doesn't have.
    with pytest.raises(InvalidScopesError):
        await token_service.get_internal_token(
            data,
            service="some-service",
            scopes=["read:some"],
            ip_address="127.0.0.1",
        )

    # Creating another internal token from the same parent token with the same
    # parameters just returns the same internal token as before.
    new_internal_token = await token_service.get_internal_token(
        data,
        service="some-service",
        scopes=["read:all"],
        ip_address="127.0.0.1",
    )
    assert internal_token == new_internal_token

    # Try again with the cache cleared to force a database lookup.
    await token_service._token_cache.clear()
    async with factory.session.begin():
        new_internal_token = await token_service.get_internal_token(
            data,
            service="some-service",
            scopes=["read:all"],
            ip_address="127.0.0.1",
        )
    assert internal_token == new_internal_token

    async with factory.session.begin():
        history = await token_service.get_change_history(
            data, token=internal_token.key, username=data.username)
    assert history.entries == [
        TokenChangeHistoryEntry(
            token=internal_token.key,
            username=data.username,
            token_type=TokenType.internal,
            parent=data.token.key,
            service="some-service",
            scopes=["read:all"],
            expires=data.expires,
            actor=data.username,
            action=TokenChange.create,
            ip_address="2001:db8::45",
            event_time=info.created,
        )
    ]

    # It's possible we'll have a race condition where two workers both create
    # an internal token at the same time with the same parameters.  Gafaelfawr
    # 3.0.2 had a regression where, once that had happened, it could not
    # retrieve the internal token because it didn't expect multiple results
    # from the query.  Simulate this and make sure it's handled properly.  The
    # easiest way to do this is to use the internals of the token service.
    second_internal_token = Token()
    created = current_datetime()
    expires = created + config.token_lifetime
    internal_token_data = TokenData(
        token=second_internal_token,
        username=data.username,
        token_type=TokenType.internal,
        scopes=["read:all"],
        created=created,
        expires=expires,
        name=data.name,
        email=data.email,
        uid=data.uid,
        groups=data.groups,
    )
    await token_service._token_redis_store.store_data(internal_token_data)
    async with factory.session.begin():
        await token_service._token_db_store.add(internal_token_data,
                                                service="some-service",
                                                parent=data.token.key)
    await token_service._token_cache.clear()
    async with factory.session.begin():
        dup_internal_token = await token_service.get_internal_token(
            data,
            service="some-service",
            scopes=["read:all"],
            ip_address="127.0.0.1",
        )
    assert dup_internal_token in (internal_token, second_internal_token)

    # A different scope or a different service results in a new token.
    async with factory.session.begin():
        new_internal_token = await token_service.get_internal_token(
            data,
            service="some-service",
            scopes=["exec:admin"],
            ip_address="127.0.0.1",
        )
    assert internal_token != new_internal_token
    async with factory.session.begin():
        new_internal_token = await token_service.get_internal_token(
            data,
            service="another-service",
            scopes=["read:all"],
            ip_address="127.0.0.1",
        )
    assert internal_token != new_internal_token

    # Check that the expiration time is capped by creating a user token that
    # doesn't expire and then creating a notebook token from it.  Use this to
    # test a token with empty scopes.
    async with factory.session.begin():
        user_token = await token_service.create_user_token(
            data,
            data.username,
            token_name="some token",
            scopes=["exec:admin"],
            expires=None,
            ip_address="127.0.0.1",
        )
    data = await token_service.get_data(user_token)
    assert data
    async with factory.session.begin():
        new_internal_token = await token_service.get_internal_token(
            data, service="some-service", scopes=[], ip_address="127.0.0.1")
        assert new_internal_token != internal_token
        info = await token_service.get_token_info_unchecked(
            new_internal_token.key)
    assert info and info.scopes == []
    expires = info.created + timedelta(minutes=config.issuer.exp_minutes)
    assert info.expires == expires
Example #17
0
async def test_notebook_token(setup: SetupTest) -> None:
    user_info = TokenUserInfo(
        username="******",
        name="Example Person",
        uid=4137,
        groups=[TokenGroup(name="foo", id=1000)],
    )
    token_service = setup.factory.create_token_service()
    session_token = await token_service.create_session_token(
        user_info,
        scopes=["read:all", "exec:admin", "user:token"],
        ip_address="127.0.0.1",
    )
    data = await token_service.get_data(session_token)
    assert data

    token = await token_service.get_notebook_token(data, ip_address="1.0.0.1")
    assert await token_service.get_user_info(token) == user_info
    info = token_service.get_token_info_unchecked(token.key)
    assert info and info == TokenInfo(
        token=token.key,
        username=user_info.username,
        token_type=TokenType.notebook,
        scopes=["exec:admin", "read:all", "user:token"],
        created=info.created,
        last_used=None,
        expires=data.expires,
        parent=session_token.key,
    )
    assert_is_now(info.created)
    assert await token_service.get_data(token) == TokenData(
        token=token,
        username=user_info.username,
        token_type=TokenType.notebook,
        scopes=["exec:admin", "read:all", "user:token"],
        created=info.created,
        expires=data.expires,
        name=user_info.name,
        uid=user_info.uid,
        groups=user_info.groups,
    )

    # Creating another notebook token from the same parent token just returns
    # the same notebook token as before.
    new_token = await token_service.get_notebook_token(
        data, ip_address="127.0.0.1"
    )
    assert token == new_token

    history = token_service.get_change_history(
        data, token=token.key, username=data.username
    )
    assert history.entries == [
        TokenChangeHistoryEntry(
            token=token.key,
            username=data.username,
            token_type=TokenType.notebook,
            parent=data.token.key,
            scopes=["exec:admin", "read:all", "user:token"],
            expires=data.expires,
            actor=data.username,
            action=TokenChange.create,
            ip_address="1.0.0.1",
            event_time=info.created,
        )
    ]

    # Check that the expiration time is capped by creating a user token that
    # doesn't expire and then creating a notebook token from it.
    user_token = await token_service.create_user_token(
        data,
        data.username,
        token_name="some token",
        scopes=[],
        expires=None,
        ip_address="127.0.0.1",
    )
    data = await token_service.get_data(user_token)
    assert data
    new_token = await token_service.get_notebook_token(
        data, ip_address="127.0.0.1"
    )
    assert new_token != token
    info = token_service.get_token_info_unchecked(new_token.key)
    assert info
    expires = info.created + timedelta(minutes=setup.config.issuer.exp_minutes)
    assert info.expires == expires
Example #18
0
async def test_wrong_user(setup: SetupTest) -> None:
    token_data = await setup.create_session_token()
    csrf = await setup.login(token_data.token)
    token_service = setup.factory.create_token_service()
    user_info = TokenUserInfo(username="******",
                              name="Some Other Person",
                              uid=137123)
    other_session_token = await token_service.create_session_token(
        user_info, scopes=["user:token"], ip_address="127.0.0.1")
    other_session_data = await token_service.get_data(other_session_token)
    assert other_session_data
    other_token = await token_service.create_user_token(
        other_session_data,
        "other-person",
        token_name="foo",
        scopes=[],
        ip_address="127.0.0.1",
    )

    # Get a token list.
    r = await setup.client.get("/auth/api/v1/users/other-person/tokens")
    assert r.status_code == 403
    assert r.json()["detail"][0]["type"] == "permission_denied"

    # Create a new user token.
    r = await setup.client.post(
        "/auth/api/v1/users/other-person/tokens",
        headers={"X-CSRF-Token": csrf},
        json={"token_name": "happy token"},
    )
    assert r.status_code == 403
    assert r.json()["detail"][0]["type"] == "permission_denied"

    # Get an individual token.
    r = await setup.client.get(
        f"/auth/api/v1/users/other-person/tokens/{other_token.key}")
    assert r.status_code == 403
    assert r.json()["detail"][0]["type"] == "permission_denied"

    # Get the history of an individual token.
    r = await setup.client.get(
        f"/auth/api/v1/users/other-person/tokens/{other_token.key}"
        "/change-history")
    assert r.status_code == 403
    assert r.json()["detail"][0]["type"] == "permission_denied"

    # Ensure you can't see someone else's token under your username either.
    r = await setup.client.get(
        f"/auth/api/v1/users/{token_data.username}/tokens/{other_token.key}")
    assert r.status_code == 404

    # Or their history.
    r = await setup.client.get(
        f"/auth/api/v1/users/{token_data.username}/tokens/{other_token.key}"
        "/change-history")
    assert r.status_code == 404

    # Delete a token.
    r = await setup.client.delete(
        f"/auth/api/v1/users/other-person/tokens/{other_token.key}",
        headers={"X-CSRF-Token": csrf},
    )
    assert r.status_code == 403
    assert r.json()["detail"][0]["type"] == "permission_denied"
    r = await setup.client.delete(
        f"/auth/api/v1/users/{token_data.username}/tokens/{other_token.key}",
        headers={"X-CSRF-Token": csrf},
    )
    assert r.status_code == 404

    # Modify a token.
    r = await setup.client.patch(
        f"/auth/api/v1/users/other-person/tokens/{other_token.key}",
        json={"token_name": "happy token"},
        headers={"X-CSRF-Token": csrf},
    )
    assert r.status_code == 403
    assert r.json()["detail"][0]["type"] == "permission_denied"
    r = await setup.client.patch(
        f"/auth/api/v1/users/{token_data.username}/tokens/{other_token.key}",
        json={"token_name": "happy token"},
        headers={"X-CSRF-Token": csrf},
    )
    assert r.status_code == 404
Example #19
0
async def test_token_from_admin_request(factory: ComponentFactory) -> None:
    user_info = TokenUserInfo(username="******",
                              name="Example Person",
                              uid=4137)
    token_service = factory.create_token_service()
    async with factory.session.begin():
        token = await token_service.create_session_token(
            user_info, scopes=[], ip_address="127.0.0.1")
    data = await token_service.get_data(token)
    assert data
    expires = current_datetime() + timedelta(days=2)
    request = AdminTokenRequest(
        username="******",
        token_type=TokenType.user,
        token_name="some token",
        scopes=["read:all"],
        expires=expires,
        name="Other User",
        uid=1345,
        groups=[TokenGroup(name="some-group", id=4133)],
    )

    # Cannot create a token via admin request because the authentication
    # information is missing the admin:token scope.
    with pytest.raises(PermissionDeniedError):
        await token_service.create_token_from_admin_request(
            request, data, ip_address="127.0.0.1")

    # Get a token with an appropriate scope.
    async with factory.session.begin():
        session_token = await token_service.create_session_token(
            user_info, scopes=["admin:token"], ip_address="127.0.0.1")
    admin_data = await token_service.get_data(session_token)
    assert admin_data

    # Test a few more errors.
    request.scopes = ["bogus:scope"]
    with pytest.raises(InvalidScopesError):
        await token_service.create_token_from_admin_request(
            request, admin_data, ip_address="127.0.0.1")
    request.scopes = ["read:all"]
    request.expires = current_datetime()
    with pytest.raises(InvalidExpiresError):
        await token_service.create_token_from_admin_request(
            request, admin_data, ip_address="127.0.0.1")
    request.expires = expires

    # Try a successful request.
    async with factory.session.begin():
        token = await token_service.create_token_from_admin_request(
            request, admin_data, ip_address="127.0.0.1")
    user_data = await token_service.get_data(token)
    assert user_data and user_data == TokenData(
        token=token, created=user_data.created, **request.dict())
    assert_is_now(user_data.created)

    async with factory.session.begin():
        history = await token_service.get_change_history(
            admin_data, token=token.key, username=request.username)
    assert history.entries == [
        TokenChangeHistoryEntry(
            token=token.key,
            username=request.username,
            token_type=TokenType.user,
            token_name=request.token_name,
            scopes=["read:all"],
            expires=request.expires,
            actor=admin_data.username,
            action=TokenChange.create,
            ip_address="127.0.0.1",
            event_time=user_data.created,
        )
    ]

    # Non-admins can't see other people's tokens.
    with pytest.raises(PermissionDeniedError):
        await token_service.get_change_history(data,
                                               token=token.key,
                                               username=request.username)

    # Now request a service token with minimal data instead.
    request = AdminTokenRequest(username="******",
                                token_type=TokenType.service)
    async with factory.session.begin():
        token = await token_service.create_token_from_admin_request(
            request, admin_data, ip_address="127.0.0.1")
    service_data = await token_service.get_data(token)
    assert service_data and service_data == TokenData(
        token=token, created=service_data.created, **request.dict())
    assert_is_now(service_data.created)

    async with factory.session.begin():
        history = await token_service.get_change_history(
            admin_data, token=token.key, username=request.username)
    assert history.entries == [
        TokenChangeHistoryEntry(
            token=token.key,
            username=request.username,
            token_type=TokenType.service,
            scopes=[],
            expires=None,
            actor=admin_data.username,
            action=TokenChange.create,
            ip_address="127.0.0.1",
            event_time=service_data.created,
        )
    ]
Example #20
0
async def test_session_token(config: Config,
                             factory: ComponentFactory) -> None:
    token_service = factory.create_token_service()
    user_info = TokenUserInfo(
        username="******",
        name="Example Person",
        uid=4137,
        groups=[
            TokenGroup(name="group", id=1000),
            TokenGroup(name="another", id=3134),
        ],
    )

    async with factory.session.begin():
        token = await token_service.create_session_token(
            user_info, scopes=["user:token"], ip_address="127.0.0.1")
    data = await token_service.get_data(token)
    assert data and data == TokenData(
        token=token,
        username="******",
        token_type=TokenType.session,
        scopes=["user:token"],
        created=data.created,
        expires=data.expires,
        name="Example Person",
        uid=4137,
        groups=[
            TokenGroup(name="group", id=1000),
            TokenGroup(name="another", id=3134),
        ],
    )
    assert_is_now(data.created)
    expires = data.created + timedelta(minutes=config.issuer.exp_minutes)
    assert data.expires == expires

    async with factory.session.begin():
        info = await token_service.get_token_info_unchecked(token.key)
    assert info and info == TokenInfo(
        token=token.key,
        username=user_info.username,
        token_name=None,
        token_type=TokenType.session,
        scopes=data.scopes,
        created=int(data.created.timestamp()),
        last_used=None,
        expires=int(data.expires.timestamp()),
        parent=None,
    )
    assert await token_service.get_user_info(token) == user_info

    async with factory.session.begin():
        history = await token_service.get_change_history(
            data, token=token.key, username=data.username)
    assert history.entries == [
        TokenChangeHistoryEntry(
            token=token.key,
            username=data.username,
            token_type=TokenType.session,
            scopes=["user:token"],
            expires=data.expires,
            actor=data.username,
            action=TokenChange.create,
            ip_address="127.0.0.1",
            event_time=data.created,
        )
    ]

    # Test a session token with scopes.
    async with factory.session.begin():
        token = await token_service.create_session_token(
            user_info,
            scopes=["read:all", "exec:admin"],
            ip_address="127.0.0.1",
        )
        data = await token_service.get_data(token)
        assert data and data.scopes == ["exec:admin", "read:all"]
        info = await token_service.get_token_info_unchecked(token.key)
        assert info and info.scopes == ["exec:admin", "read:all"]
Example #21
0
async def build_history(
    setup: SetupTest,
) -> List[TokenChangeHistoryEntry]:
    """Perform a bunch of token manipulations and return the history entries.

    Assume that all token manipulations generate the correct history entries,
    since that's tested in other tests.  The only point of this function is to
    build enough history that we can make interesting paginated queries of it.
    """
    token_service = setup.factory.create_token_service()

    user_info_one = TokenUserInfo(username="******")
    token_one = await token_service.create_session_token(
        user_info_one,
        scopes=["exec:test", "read:all", "user:token"],
        ip_address="192.0.2.3",
    )
    token_data_one = await token_service.get_data(token_one)
    assert token_data_one
    await token_service.get_internal_token(
        token_data_one,
        "foo",
        scopes=["exec:test", "read:all"],
        ip_address="192.0.2.4",
    )
    internal_token_one_bar = await token_service.get_internal_token(
        token_data_one, "bar", scopes=["read:all"], ip_address="192.0.2.3"
    )
    token_data_internal_one_bar = await token_service.get_data(
        internal_token_one_bar
    )
    assert token_data_internal_one_bar
    await token_service.get_internal_token(
        token_data_internal_one_bar, "baz", scopes=[], ip_address="10.10.10.10"
    )
    notebook_token_one = await token_service.get_notebook_token(
        token_data_one, ip_address="198.51.100.5"
    )
    token_data_notebook_one = await token_service.get_data(notebook_token_one)
    assert token_data_notebook_one
    await token_service.get_internal_token(
        token_data_notebook_one,
        "foo",
        scopes=["exec:test"],
        ip_address="10.10.10.20",
    )

    user_info_two = TokenUserInfo(username="******")
    token_two = await token_service.create_session_token(
        user_info_two,
        scopes=["read:some", "user:token"],
        ip_address="192.0.2.20",
    )
    token_data_two = await token_service.get_data(token_two)
    assert token_data_two
    user_token_two = await token_service.create_user_token(
        token_data_two,
        token_data_two.username,
        token_name="some token",
        scopes=["read:some", "user:token"],
        ip_address="192.0.2.20",
    )
    token_data_user_two = await token_service.get_data(user_token_two)
    assert token_data_user_two
    await token_service.get_internal_token(
        token_data_user_two,
        "foo",
        scopes=["read:some"],
        ip_address="10.10.10.10",
    )
    assert await token_service.modify_token(
        user_token_two.key,
        token_data_user_two,
        token_data_user_two.username,
        ip_address="192.0.2.20",
        token_name="happy token",
    )

    request = AdminTokenRequest(
        username="******",
        token_type=TokenType.service,
        scopes=["admin:token"],
    )
    service_token = await token_service.create_token_from_admin_request(
        request,
        TokenData.bootstrap_token(),
        ip_address="2001:db8:034a:ea78:4278:4562:6578:9876",
    )
    service_token_data = await token_service.get_data(service_token)
    assert service_token_data
    assert await token_service.modify_token(
        user_token_two.key,
        service_token_data,
        ip_address="2001:db8:034a:ea78:4278:4562:6578:9876",
        scopes=["admin:token", "read:all"],
    )
    assert await token_service.modify_token(
        user_token_two.key,
        service_token_data,
        ip_address="2001:db8:034a:ea78:4278:4562:6578:af42",
        token_name="other name",
        expires=current_datetime() + timedelta(days=30),
        scopes=["read:all"],
    )
    assert await token_service.delete_token(
        token_one.key,
        service_token_data,
        username=token_data_one.username,
        ip_address="2001:db8:034a:ea78:4278:4562:6578:9876",
    )

    # Spread out the timestamps so that we can test date range queries.  Every
    # other entry has the same timestamp as the previous entry to test that
    # queries handle entries with the same timestamp.
    entries = (
        setup.session.query(TokenChangeHistory)
        .order_by(TokenChangeHistory.id)
        .all()
    )
    event_time = current_datetime() - timedelta(seconds=len(entries) * 5)
    with setup.transaction():
        for i, entry in enumerate(entries):
            entry.event_time = event_time
            if i % 2 != 0:
                event_time += timedelta(seconds=5)

    history = token_service.get_change_history(service_token_data)
    assert history.count == 20
    assert len(history.entries) == 20
    return history.entries
Example #22
0
async def test_modify(factory: ComponentFactory) -> None:
    user_info = TokenUserInfo(username="******",
                              name="Example Person",
                              uid=4137)
    token_service = factory.create_token_service()
    async with factory.session.begin():
        session_token = await token_service.create_session_token(
            user_info,
            scopes=["read:all", "user:token"],
            ip_address="127.0.0.1",
        )
    data = await token_service.get_data(session_token)
    assert data
    async with factory.session.begin():
        user_token = await token_service.create_user_token(
            data,
            data.username,
            token_name="some-token",
            scopes=[],
            ip_address="127.0.0.1",
        )

    expires = current_datetime() + timedelta(days=50)
    async with factory.session.begin():
        await token_service.modify_token(
            user_token.key,
            data,
            token_name="happy token",
            ip_address="127.0.0.1",
        )
        await token_service.modify_token(
            user_token.key,
            data,
            scopes=["read:all"],
            expires=expires,
            ip_address="192.168.0.4",
        )
        info = await token_service.get_token_info_unchecked(user_token.key)
    assert info and info == TokenInfo(
        token=user_token.key,
        username="******",
        token_type=TokenType.user,
        token_name="happy token",
        scopes=["read:all"],
        created=info.created,
        expires=expires,
        last_used=None,
        parent=None,
    )
    async with factory.session.begin():
        await token_service.modify_token(
            user_token.key,
            data,
            expires=None,
            no_expire=True,
            ip_address="127.0.4.5",
        )

    async with factory.session.begin():
        history = await token_service.get_change_history(
            data, token=user_token.key, username=data.username)
    assert history.entries == [
        TokenChangeHistoryEntry(
            token=user_token.key,
            username=data.username,
            token_type=TokenType.user,
            token_name="happy token",
            scopes=["read:all"],
            expires=None,
            actor=data.username,
            action=TokenChange.edit,
            old_expires=expires,
            ip_address="127.0.4.5",
            event_time=history.entries[0].event_time,
        ),
        TokenChangeHistoryEntry(
            token=user_token.key,
            username=data.username,
            token_type=TokenType.user,
            token_name="happy token",
            scopes=["read:all"],
            expires=expires,
            actor=data.username,
            action=TokenChange.edit,
            old_scopes=[],
            old_expires=None,
            ip_address="192.168.0.4",
            event_time=history.entries[1].event_time,
        ),
        TokenChangeHistoryEntry(
            token=user_token.key,
            username=data.username,
            token_type=TokenType.user,
            token_name="happy token",
            scopes=[],
            expires=None,
            actor=data.username,
            action=TokenChange.edit,
            old_token_name="some-token",
            ip_address="127.0.0.1",
            event_time=history.entries[2].event_time,
        ),
        TokenChangeHistoryEntry(
            token=user_token.key,
            username=data.username,
            token_type=TokenType.user,
            token_name="some-token",
            scopes=[],
            expires=None,
            actor=data.username,
            action=TokenChange.create,
            ip_address="127.0.0.1",
            event_time=info.created,
        ),
    ]