Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b5b6762
release updates
longshuicy Jul 22, 2024
d655342
Merge branch 'main' into release/v2.0-beta-3
longshuicy Jul 24, 2024
4d136e8
1147 clowder 2 helm chart clean up (#1148)
longshuicy Jul 25, 2024
91ad4e7
turn off default extractors
longshuicy Jul 26, 2024
dfcdbde
Merge branch 'main' into release/v2.0-beta-3
longshuicy Aug 13, 2024
0a64e47
Remove helm chart env (#1165)
longshuicy Aug 22, 2024
8c9eb8a
set workers based on recommended number of cores (#1170)
longshuicy Aug 27, 2024
90d65ed
1168 add contents to docs (#1188)
longshuicy Aug 29, 2024
c6e1267
Merge branch 'main' into release/v2.0-beta-3
longshuicy Aug 29, 2024
a28c5fb
error message when sharing dataset with user (#1140)
tcnichol Aug 30, 2024
1712540
Merge branch 'main' into release/v2.0-beta-3
lmarini Sep 17, 2024
f8588a7
Fix/beta 3 extractions list (#1199)
lmarini Nov 5, 2024
b049af7
Make sure only dataset extractors are shown on the analysis tab for a…
lmarini Nov 5, 2024
4ea5404
When adding metadata, sometimes two instances of it would show up aft…
lmarini Nov 5, 2024
9a41c8a
Rabbitmq host update to release name (#1203)
longshuicy Nov 5, 2024
15c47d2
Fix refresh token issue when using password flow (#1205)
GalMunGral Nov 5, 2024
700aeb1
Fix edit file metadata bug by making sure (#1207)
lmarini Nov 5, 2024
76f5353
Fix/authenticated datasets access (#1210)
lmarini Nov 11, 2024
96a61d1
Add endpoint for updating user info (#1206)
GalMunGral Nov 11, 2024
f01c46a
Default to None if "owner" doesn't exists in msg (#1211)
lmarini Nov 13, 2024
dba247a
Use healthcheck to make sure elasticsearch has (#1212)
lmarini Nov 15, 2024
70f0fb8
Only show Create Feed button if in admin mode. (#1213)
lmarini Nov 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ scripts/keycloak/data/*
# ignore clowder chart deps
deployments/kubernetes/charts/clowder2/charts
deployments/kubernetes/charts/clowder2/*clowder2-software-dev.yaml
*secret*.yaml

# Environments
.env
Expand Down
3 changes: 1 addition & 2 deletions .run/uvicorn.run.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="uvicorn" type="PythonConfigurationType" factoryName="Python" nameIsGenerated="true">
<module name="clowder2" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
Expand All @@ -14,7 +13,7 @@
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="uvicorn" />
<option name="PARAMETERS" value="app.main:app --reload --host 0.0.0.0" />
<option name="PARAMETERS" value="app.main:app --host 0.0.0.0 --workers 17" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="true" />
Expand Down
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,34 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [v2.0.0-beta.3] - 2024-07-29

### Added

- License management
- Release dataset with versions
- Enable and disable user account through Keycloak
- Jupyterhub integration
- Interface for creating and editing matching criteria for triggering extractors
- Interface for editing metadata definitions
- My dataset tab listing all the datasets created by the user
- Drag and drop upload multiple files
- Footer with links to documentation, source code, and contact information
- Documentation through MKDocs

### Changed

- Allow public datasets and files to be searchable
- List all the extractors with the ability to enable/disable the extractors
- Filter listeners based on their support for file or dataset
- Helm chart updated to support custom existing secret

### Fixed

- Clowder registration link on the top bar
- Case-insensitive search
- Download count immediately increments after download

## [v2.0.0-beta.2] - 2024-02-16

### Added
Expand Down
3 changes: 2 additions & 1 deletion backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ ENV PATH="/code/.venv/bin:$PATH"
COPY ./app /code/app

# launch app using uvicorn
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
# Number of recommended workers is 2 x number_of_cores +1
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--workers", "17"]
2 changes: 1 addition & 1 deletion backend/app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Settings(BaseSettings):
API_V2_STR: str = "/api/v2"
admin_email: str = "devnull@ncsa.illinois.edu"
frontend_url: str = "http://localhost:3000"
version: str = "2.0.0-beta.2"
version: str = "2.0.0-beta.3"

# Unique secret for hashing API keys. Generate with `openssl rand -hex 32`
local_auth_secret = "clowder_secret_key"
Expand Down
38 changes: 38 additions & 0 deletions backend/app/keycloak_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import json
import logging
from datetime import datetime
from typing import Optional

from fastapi import Depends, HTTPException, Security
from fastapi.security import APIKeyCookie, APIKeyHeader, OAuth2AuthorizationCodeBearer
Expand Down Expand Up @@ -435,6 +436,43 @@ async def create_user(email: str, password: str, firstName: str, lastName: str):
return user


async def update_user(
email: str,
new_email: Optional[str],
new_password: Optional[str],
new_firstName: Optional[str],
new_lastName: Optional[str],
):
"""Update existing user in Keycloak."""
keycloak_admin = KeycloakAdmin(
server_url=settings.auth_server_url,
username=settings.keycloak_username,
password=settings.keycloak_password,
realm_name=settings.keycloak_realm_name,
user_realm_name=settings.keycloak_user_realm_name,
# client_secret_key=settings.auth_client_secret,
# client_id=settings.keycloak_client_id,
verify=True,
)
existing_user_id = keycloak_admin.get_user_id(email)
existing_user = keycloak_admin.get_user(existing_user_id)
# Update user and set password
keycloak_admin.update_user(
existing_user_id,
{
"email": new_email or existing_user["email"],
"username": new_email or existing_user["email"],
"firstName": new_firstName or existing_user["firstName"],
"lastName": new_lastName or existing_user["lastName"],
},
)
if new_password:
keycloak_admin.set_user_password(existing_user_id, new_password, False)

updated_user = keycloak_admin.get_user(existing_user_id)
return updated_user


def delete_user(email: str):
"""Create a user in Keycloak."""
keycloak_admin = KeycloakAdmin(
Expand Down
6 changes: 4 additions & 2 deletions backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
description="A cloud native data management framework to support any research domain. Clowder was "
"developed to help researchers and scientists in data intensive domains manage raw data, complex "
"metadata, and automatic data pipelines. ",
version="2.0.0-beta.2",
version="2.0.0-beta.3",
contact={"name": "Clowder", "url": "https://clowderframework.org/"},
license_info={
"name": "Apache 2.0",
Expand Down Expand Up @@ -316,7 +316,9 @@ async def startup_beanie():
ThumbnailDBViewList,
LicenseDB,
],
recreate_views=True,
# If view exists, will not recreate
# When view query changes, make sure to manually drop view and recreate
recreate_views=False,
)


Expand Down
8 changes: 5 additions & 3 deletions backend/app/models/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,9 +307,11 @@ async def validate_context(
detail="Context is required",
)
if context is not None:
pass
# TODO validate context
return content
if context_url is not None:
pass
# TODO validate context
return content
if definition is not None:
if (
md_def := await MetadataDefinitionDB.find_one(
Expand All @@ -322,7 +324,7 @@ async def validate_context(
status_code=400,
detail=f"{definition} is not valid metadata definition",
)
return content
return content


def deep_update(orig: dict, new: dict):
Expand Down
6 changes: 6 additions & 0 deletions backend/app/models/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ class UserIn(UserBase):
password: str


class UserUpdate(BaseModel):
first_name: Optional[str]
last_name: Optional[str]
password: Optional[str]


class UserLogin(BaseModel):
email: EmailStr
password: str
Expand Down
45 changes: 44 additions & 1 deletion backend/app/routers/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@
enable_disable_user,
get_current_user,
keycloak_openid,
update_user,
)
from app.models.datasets import DatasetDBViewList
from app.models.users import UserDB, UserIn, UserLogin, UserOut
from app.models.users import UserDB, UserIn, UserLogin, UserOut, UserUpdate
from app.routers.utils import save_refresh_token
from beanie import PydanticObjectId
from fastapi import APIRouter, Depends, HTTPException
from keycloak.exceptions import (
KeycloakAuthenticationError,
KeycloakGetError,
KeycloakPostError,
KeycloakPutError,
)
from passlib.hash import bcrypt

Expand Down Expand Up @@ -69,6 +72,7 @@ async def save_user(userIn: UserIn):
async def login(userIn: UserLogin):
try:
token = keycloak_openid.token(userIn.email, userIn.password)
await save_refresh_token(token["refresh_token"], userIn.email)
return {"token": token["access_token"]}
# bad credentials
except KeycloakAuthenticationError as e:
Expand All @@ -95,6 +99,45 @@ async def authenticate_user(email: str, password: str):
return user


@router.patch("/users/me", response_model=UserOut)
async def update_current_user(
userUpdate: UserUpdate, current_user=Depends(get_current_user)
):
try:
await update_user(
current_user.email,
None,
userUpdate.password,
userUpdate.first_name,
userUpdate.last_name,
)
except KeycloakGetError as e:
raise HTTPException(
status_code=e.response_code,
detail=json.loads(e.error_message),
headers={"WWW-Authenticate": "Bearer"},
)
except KeycloakPutError as e:
raise HTTPException(
status_code=e.response_code,
detail=json.loads(e.error_message),
headers={"WWW-Authenticate": "Bearer"},
)

# Update local user
user = await UserDB.find_one(UserDB.email == current_user.email)

if userUpdate.first_name:
user.first_name = userUpdate.first_name
if userUpdate.last_name:
user.last_name = userUpdate.last_name
if userUpdate.password:
user.hashed_password = bcrypt.hash(userUpdate.password)

await user.save()
return user.dict()


@router.get("/users/me/is_admin", response_model=bool)
async def get_admin(
dataset_id: str = None, current_username=Depends(get_current_user)
Expand Down
Loading
Loading