Skip to content

Commit

Permalink
Merge pull request #733 from oss-aspen/dev
Browse files Browse the repository at this point in the history
dev to main update
  • Loading branch information
cdolfi authored Oct 10, 2024
2 parents 4980eb6 + 630e5a6 commit b150c4b
Show file tree
Hide file tree
Showing 13 changed files with 78 additions and 24 deletions.
10 changes: 10 additions & 0 deletions .wordlist-md
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,13 @@ filesystem
Podman
credsStore
credStore
codebase
oauth
FlaskLogin
JSON
UI
UUID
backend
href's
TLS
OAuth's
4 changes: 2 additions & 2 deletions 8Knot/pages/affiliation/visualizations/commit_domains.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,8 @@ def process_data(df: pd.DataFrame, num, start_date, end_date):
# creates list of emails for each contribution and flattens list result
emails = df.author_email.tolist()

# remove any entries not in email format
emails = [x for x in emails if "@" in x]
# remove any entries not in email format and put all emails in lowercase
emails = [x.lower() for x in emails if "@" in x]

# creates list of email domains from the emails list
email_domains = [x[x.rindex("@") + 1 :] for x in emails]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,8 @@ def process_data(df: pd.DataFrame, num, start_date, end_date, email_filter):
# creates list of emails for each contribution and flattens list result
emails = df.email_list.str.split(" , ").explode("email_list").tolist()

# remove any entries not in email format
emails = [x for x in emails if "@" in x]
# remove any entries not in email format and flattens list result
emails = [x.lower() for x in emails if "@" in x]

# creates list of email domains from the emails list
email_domains = [x[x.rindex("@") + 1 :] for x in emails]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,8 @@ def process_data(df: pd.DataFrame, contributions, contributors, start_date, end_
# creates list of unique emails and flattens list result
emails = df.email_list.str.split(" , ").explode("email_list").tolist()

# remove any entries not in email format
emails = [x for x in emails if "@" in x]
# remove any entries not in email format and flattens list result
emails = [x.lower() for x in emails if "@" in x]

# creates list of email domains from the emails list
email_domains = [x[x.rindex("@") + 1 :] for x in emails]
Expand Down
4 changes: 2 additions & 2 deletions 8Knot/pages/affiliation/visualizations/unqiue_domains.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,8 @@ def process_data(df: pd.DataFrame, num, start_date, end_date):
# creates list of unique emails and flattens list result
emails = df.email_list.str.split(" , ").explode("email_list").unique().tolist()

# remove any entries not in email format
emails = [x for x in emails if "@" in x]
# remove any entries not in email format and put all emails in lowercase
emails = [x.lower() for x in emails if "@" in x]

# creates list of email domains from the emails list
email_domains = [x[x.rindex("@") + 1 :] for x in emails]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,5 +372,10 @@ def pr_assignment(df, start_date, end_date, contrib):
(df_in_range["assignment_action"] == "assigned") & (df_in_range["assign_date"] <= end_date)
]

# return the different of assignments and unassignments
return df_assigned.shape[0] - df_unassign.shape[0]
# the different of assignments and unassignments
assign_value = df_assigned.shape[0] - df_unassign.shape[0]

# prevent negative assignments
assign_value = 0 if assign_value < 0 else assign_value

return assign_value
Original file line number Diff line number Diff line change
Expand Up @@ -369,5 +369,10 @@ def issue_assignment(df, start_date, end_date, contrib):
(df_in_range["assignment_action"] == "assigned") & (df_in_range["assign_date"] <= end_date)
]

# return the different of assignments and unassignments
return df_assigned.shape[0] - df_unassign.shape[0]
# the different of assignments and unassignments
assign_value = df_assigned.shape[0] - df_unassign.shape[0]

# prevent negative assignments
assign_value = 0 if assign_value < 0 else assign_value

return assign_value
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,9 @@
step=1,
value=2,
size="sm",
style={"width": "100px"},
),
className="me-2",
width=2,
),
dbc.Col(
width=6,
),
dbc.Col(
dbc.Button(
Expand All @@ -83,7 +80,6 @@
),
],
align="center",
justify="between",
),
]
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,9 @@
step=1,
value=2,
size="sm",
style={"width": "100px"},
),
className="me-2",
width=2,
),
dbc.Col(
width=6,
),
dbc.Col(
dbc.Button(
Expand All @@ -82,7 +79,6 @@
),
],
align="center",
justify="between",
),
]
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,10 +375,12 @@ def create_figure(df_final, threshold, step_size):
hovertemplate="%{y} people contributing to<br>%{customdata[0]}% of %{text} from<br>%{customdata[1]}<br><extra></extra>",
)

# update xaxes to show only the year
fig.update_xaxes(showgrid=True, ticklabelmode="period", dtick="M12", tickformat="%Y")

# layout styling
fig.update_layout(
xaxis_title=f"Timeline (stepsize = {step_size} months)",
xaxis=dict(tick0=start_date),
yaxis_title="Lottery Factor",
font=dict(size=14),
margin_b=40,
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ you're using.
Note: You'll have to manually fill in the \<AUGUR_APP_ID\> in the AUGUR_USER_AUTH_ENDPOINT environment variable.

In-depth instructions for enabling 8Knot + Augur integration is available in [AUGUR_LOGIN.md](docs/AUGUR_LOGIN.md).
An overview of OAuth's implementation in 8Knot can be found here: [user-accounts-in-8knot.md](docs/user-accounts-in-8knot.md).


### Runtime

Expand Down
38 changes: 38 additions & 0 deletions docs/user-accounts-in-8knot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# How do user accounts work in 8Knot (OAuth and session handling)

8Knot has user accounts- there are log in / log out buttons, and user groups are persist across logins.
However, 8Knot doesn't directly handle these accounts. Instead, Augur manages the user's accounts and 8Knot connects as an OAuth client.

This document describes how this flow works, what parts of the codebase are relevant, and where there are currently (4/22/24) gaps in the implementation.

## Notes
- `access_token` and `bearer_token` are the same thing

## How the flow works

Let's assume that we've got a functional 8Knot instance running that's been configured as an oauth client for Augur. We've documented how to connect an application to the Augur frontend as an oauth client (specifically, you can find it on the 8Knot welcome page) but let's dig deeper into how this works on the application server.

Assume:
1. Your application is configured with a valid `application_id` and `client_secret`. e.g. login, refresh, and manage groups all work as expected.

Then the following happens when the user clicks `Augur Log in / Sign up` in the UI:
1. The frontend application href's to `http://<host>/login/`.
2. `/login/` is a Flask route that the backend serves. It's defined in the file `8Knot/8Knot/_login.py`.
3. The backend route gets the OAuth provider route from the environment (`OAUTH_AUTHORIZE_URL`) and redirects to that host with the URL format: `http://<OAUTH_AUTHORIZE_URL>/?client_id=<id>&response_type=code/`.
4. The user should be routed to the Augur frontend where they can log in and authorize 8Knot to use their account.
5. When the user authorizes 8Knot's use of their account, Augur will redirect them back to the registered 8Knot application route `http://<host>/authorize/?code=<temp_auth_code>`.
6. The 8Knot backend `/authorize/` route receives this code as a query parameter in the URL and uses it, along with the application's client secret, to post a request to the `OAUTH_TOKEN_URL` intending to receive a `bearer token` and `refresh token` from the Augur frontend.
7. The Augur frontend, if all values are acceptable, returns a `bearer token`, a `refresh token`, a `token expiration` and a `username`.
8. 8Knot creates a random `id_number = str(uuid.uuid1())` for the user and stores a JSON payload `{username, access_token, refresh_token, expiration}` in Redis under that UUID.
9. Finally, `login_user(User(id_number))` is called, setting an HTTP-only `session` cookie in the client's browser that will be used by FlaskLogin to handle the user's session in the future.

## Topics out of scope for this document:

- oauth2.0 flow implementation details: [an overview](https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2)
- Flask Login: [docs](https://flask-login.readthedocs.io/en/latest/#flask_login.login_user)

## Current implementation gaps:

1. The use of the user's `access_token` is zero-shot. If authentication fails, it fails silently without notifying the user that they should log in again.
2. We don't currently user the refresh token even though it's available.
3. Session cookies are `HTTP-only` but not `Secure` (require TLS) in general, but this should only be the case in development.
2 changes: 1 addition & 1 deletion openshift/base/8k-postgres-cache.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ spec:
ports:
- containerPort: 5432
volumeMounts:
- mountPath: /var/lib/postgresql/data
- mountPath: /var/lib/pgql/data
name: postgres-cache-data
envFrom:
- secretRef:
Expand Down

0 comments on commit b150c4b

Please sign in to comment.