async def test_create_token(driver: webdriver.Chrome, selenium_config: SeleniumConfig) -> None: cookie = State(token=selenium_config.token).as_cookie() driver.header_overrides = {"Cookie": f"{COOKIE_NAME}={cookie}"} tokens_url = urljoin(selenium_config.url, "/auth/tokens") driver.get(tokens_url) tokens_page = TokensPage(driver) assert tokens_page.get_tokens(TokenType.user) == [] session_tokens = tokens_page.get_tokens(TokenType.session) assert len(session_tokens) == 1 assert session_tokens[0].token == selenium_config.token.key # Drop our cookie in favor of the one the browser is now sending, since # the browser one contains a CSRF token that will be required for token # creation. del driver.header_overrides create_modal = await tokens_page.click_create_token() create_modal.set_token_name("test token") await create_modal.submit() new_token_modal = tokens_page.get_new_token_modal() assert new_token_modal.token.startswith("gt-") new_token_modal.dismiss() user_tokens = tokens_page.get_tokens(TokenType.user) assert len(user_tokens) == 1 assert user_tokens[0].name == "test token"
async def test_login_no_auth(setup: SetupTest) -> None: r = await setup.client.get("/auth/api/v1/login") assert_unauthorized_is_correct(r, setup.config) # An Authorization header with a valid token still redirects. token_data = await setup.create_session_token() r = await setup.client.get( "/auth/api/v1/login", headers={"Authorization": f"bearer {token_data.token}"}, ) assert_unauthorized_is_correct(r, setup.config) # A token with no underlying Redis representation is ignored. state = State(token=Token()) r = await setup.client.get( "/auth/api/v1/login", cookies={COOKIE_NAME: state.as_cookie()}, ) assert_unauthorized_is_correct(r, setup.config) # Likewise with a cookie containing a malformed token. This requires a # bit more work to assemble. key = setup.config.session_secret.encode() fernet = Fernet(key) data = {"token": "bad-token"} bad_cookie = fernet.encrypt(json.dumps(data).encode()).decode() r = await setup.client.get( "/auth/api/v1/login", cookies={COOKIE_NAME: bad_cookie}, ) assert_unauthorized_is_correct(r, setup.config) # And finally check with a mangled state that won't decrypt. bad_cookie = "XXX" + state.as_cookie() r = await setup.client.get( "/auth/api/v1/login", cookies={COOKIE_NAME: bad_cookie}, ) assert_unauthorized_is_correct(r, setup.config)
async def test_auth_required(setup: SetupTest) -> None: token_data = await setup.create_session_token() token = token_data.token csrf = await setup.login(token) # Replace the cookie with one containing the CSRF token but not the # authentication token. setup.logout() setup.client.cookies[COOKIE_NAME] = State(csrf=csrf).as_cookie() r = await setup.client.post( "/auth/api/v1/tokens", headers={"X-CSRF-Token": csrf}, json={ "username": "******", "token_type": "service" }, ) assert r.status_code == 401 r = await setup.client.get("/auth/api/v1/users/example/tokens") assert r.status_code == 401 r = await setup.client.post( "/auth/api/v1/users/example/tokens", headers={"X-CSRF-Token": csrf}, json={"token_name": "some token"}, ) assert r.status_code == 401 r = await setup.client.get(f"/auth/api/v1/users/example/tokens/{token.key}" ) assert r.status_code == 401 r = await setup.client.get( f"/auth/api/v1/users/example/tokens/{token.key}/change-history") assert r.status_code == 401 r = await setup.client.delete( f"/auth/api/v1/users/example/tokens/{token.key}", headers={"X-CSRF-Token": csrf}, ) assert r.status_code == 401 r = await setup.client.patch( f"/auth/api/v1/users/example/tokens/{token.key}", headers={"X-CSRF-Token": csrf}, json={"token_name": "some token"}, ) assert r.status_code == 401
async def test_login(setup: SetupTest) -> None: token_data = await setup.create_session_token( username="******", scopes=["read:all", "exec:admin"] ) cookie = State(token=token_data.token).as_cookie() setup.client.cookies.set(COOKIE_NAME, cookie, domain=TEST_HOSTNAME) r = await setup.client.get("/auth/api/v1/login", allow_redirects=False) assert r.status_code == 200 data = r.json() expected_scopes = [ {"name": n, "description": d} for n, d in sorted(setup.config.known_scopes.items()) ] assert data == { "csrf": ANY, "username": "******", "scopes": ["exec:admin", "read:all"], "config": {"scopes": expected_scopes}, } state = State.from_cookie(r.cookies[COOKIE_NAME], None) assert state.csrf == data["csrf"] assert state.token == token_data.token
async def get_logout( return_url: Optional[str] = Depends(return_url), context: RequestContext = Depends(context_dependency), ) -> RedirectResponse: """Log out and redirect the user. The user is redirected to the URL given in the rd parameter, if any, and otherwise to the after_logout_url configuration setting. """ if context.state.token: context.logger.info("Successful logout") else: context.logger.info("Logout of already-logged-out session") context.state = State() if not return_url: return_url = context.config.after_logout_url return RedirectResponse(return_url)
async def login(self, token: Token) -> str: """Create a valid Gafaelfawr session. Add a valid Gafaelfawr session cookie to the `httpx.AsyncClient`, use the login URL, and return the resulting CSRF token. Parameters ---------- token : `gafaelfawr.models.token.Token` The token for the client identity to use. Returns ------- csrf : `str` The CSRF token to use in subsequent API requests. """ cookie = State(token=token).as_cookie() self.client.cookies.set(COOKIE_NAME, cookie, domain=TEST_HOSTNAME) r = await self.client.get("/auth/api/v1/login") assert r.status_code == 200 return r.json()["csrf"]
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()}, )