-
Notifications
You must be signed in to change notification settings - Fork 0
feat(api): adds automatic keycloak verification #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
659dbe6
581a8e7
17d3a21
9768349
d92bd1e
fd9ed59
47cfa84
bb4ec11
2d394ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| # refresh_token: dict[str, str] = ( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. are these notes intended to be committed? |
||
| # # pyright: ignore[reportUnknownVariableType] | ||
| # keycloak_openid.refresh_token( # pyright: ignore[reportUnknownMemberType] | ||
| # token["refresh_token"] | ||
| # ) | ||
| # ) | ||
| # print(refresh_token) | ||
|
|
||
| # save token and refresh token to file | ||
| # if they're present, check if access token is valid, if no but refresh token is | ||
| # , just refresh token | ||
| # if invalid, run whole thing | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| import os | ||
|
|
||
| from keycloak import KeycloakOpenID | ||
| from keycloak.pkce_utils import generate_code_challenge, generate_code_verifier | ||
|
|
||
| from python_interface_to_workflows.auth.open_auth_url import open_auth_url | ||
|
|
||
|
|
||
| def return_key(dev: bool) -> str: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think that we should configure tooling to grant ordinary users access to the staging cluster.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style suggestion: I'm not sure that I would correctly guess what keycloak_check.return_key does from this name without reading the code. consider a more descriptive name. |
||
| match dev: | ||
| case True: | ||
| keycloak_openid = KeycloakOpenID( | ||
| server_url="https://identity-test.diamond.ac.uk/", | ||
| client_id="workflows-ui-dev", | ||
| realm_name="dls", | ||
| client_secret_key="", | ||
| pool_maxsize=1, | ||
| ) | ||
| case False: | ||
| keycloak_openid = KeycloakOpenID( | ||
| client_id="workflows-dashboard", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for now it would be better to use workflows-cli. we maybe should consider a new client for the python interface. |
||
| server_url="https://identity-test.diamond.ac.uk/", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this client is intended for use on indentity, not identity-test |
||
| realm_name="dls", | ||
| client_secret_key="", | ||
| pool_maxsize=1, | ||
| ) | ||
| code_verifier = generate_code_verifier() | ||
| code_challenge, code_challenge_method = generate_code_challenge(code_verifier) | ||
| auth_url = keycloak_openid.auth_url( | ||
| redirect_uri="http://localhost:5173/", | ||
| scope="openid posix-uid profile email fedid", | ||
| state="", | ||
| code_challenge=code_challenge, | ||
| code_challenge_method=code_challenge_method, | ||
| ) | ||
| open_auth_url(auth_url) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what happens when a user is submitting from a headless terminal? |
||
| token: dict[str, str] = ( # pyright: ignore[reportUnknownVariableType] | ||
| keycloak_openid.token( # pyright: ignore[reportUnknownMemberType] | ||
| grant_type="authorization_code", | ||
| code=os.environ["AUTH"], | ||
| redirect_uri="http://localhost:5173/", | ||
| code_verifier=code_verifier, | ||
| ) | ||
| ) | ||
| os.environ["REFRESH"] = token["refresh_token"] | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why are you storing this in the python process environment variables? |
||
| os.environ["TOKEN"] = token["access_token"] | ||
| return token["access_token"] # pyright: ignore[reportUnknownArgumentType] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import os | ||
| import socket | ||
| import urllib.parse | ||
| import webbrowser | ||
| from http.server import BaseHTTPRequestHandler, HTTPServer | ||
| from typing import cast | ||
|
|
||
|
|
||
| class _ReusingHTTPServer(HTTPServer): | ||
| allow_reuse_address = True | ||
| auth_code: str | ||
|
|
||
|
|
||
| class CallbackHandler(BaseHTTPRequestHandler): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does this need to be public? |
||
| def do_GET(self): | ||
| query = urllib.parse.urlparse(self.path).query | ||
| params = urllib.parse.parse_qs(query) | ||
| if "code" in params: | ||
| cast(_ReusingHTTPServer, self.server).auth_code = params["code"][0] | ||
| self.send_response(200) | ||
| self.end_headers() | ||
| self.wfile.write(b"Authorization successful. You can close this window.") | ||
| else: | ||
| self.send_response(400) | ||
| self.end_headers() | ||
| self.wfile.write(b"Missing authorization code.") | ||
|
|
||
|
|
||
| def open_auth_url(auth_url: str): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing return type hint |
||
| httpd = _ReusingHTTPServer(("localhost", 5173), CallbackHandler) | ||
| webbrowser.open(auth_url) | ||
| try: | ||
| httpd.handle_request() | ||
| os.environ["AUTH"] = httpd.auth_code | ||
| except OSError: | ||
| os.environ["AUTH"] = "" | ||
| print("ERROR: Port in use. Please restart your terminal.") | ||
| exit(1) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style suggestion: consider the principle of least surprise: would a caller of this function expect it to exit the process on error? |
||
| finally: | ||
| httpd.socket.shutdown(socket.SHUT_RDWR) | ||
| httpd.server_close() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,14 @@ | ||
| from gql import Client, gql | ||
| from gql.transport.aiohttp import AIOHTTPTransport | ||
|
|
||
| from python_interface_to_workflows.auth.keycloak_checker import return_key | ||
|
|
||
|
|
||
| def submit_to_graphql(): | ||
| token = return_key(dev=True) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment: the current implementation requires the user to open a browser and log in every time that submit_to_graphql is called. if you are not already planning to do so, you may consider revising this approach in future PRs. |
||
| transport = AIOHTTPTransport( | ||
| url="https://staging.workflows.diamond.ac.uk/graphql", | ||
| headers={"Authorization": "Bearer "}, # after Bearer = access token | ||
| headers={"Authorization": f"Bearer {token}"}, | ||
| ) | ||
| client = Client( | ||
| transport=transport, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.