Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
49f1254
chore: remove google auth verification
ChrOertlin Jan 28, 2025
b8d6103
Merge branch 'master' into feat-keycloak
ChrOertlin Jan 28, 2025
5d348f7
feat: add python-keycloak dependency
ChrOertlin Jan 28, 2025
d6c2eeb
Add keycloak client
ahdamin Jan 28, 2025
2ae86a3
chore: test
ChrOertlin Jan 28, 2025
4f617a4
feat: implement auth service
ChrOertlin Feb 6, 2025
ac275da
feat: fix before request
ChrOertlin Feb 6, 2025
b2f3397
chore: add more user util
ChrOertlin Feb 6, 2025
b613bed
refactor: create user service for user operations
ChrOertlin Feb 6, 2025
43f4a12
feat: register user service in flask app
ChrOertlin Feb 6, 2025
fddb478
revert: introduction of authenticated user model
ChrOertlin Feb 6, 2025
9253efd
feat: redirect login button to new flow
ChrOertlin Feb 6, 2025
96cea98
chore: add redirect to appconfig
ChrOertlin Feb 6, 2025
3cffc98
fix: unused import
ChrOertlin Feb 6, 2025
6f8404b
feat: working prototype
ChrOertlin Feb 17, 2025
86b6466
chore: merge master
ChrOertlin Feb 17, 2025
90eaf4b
chore: test pass
ChrOertlin Feb 17, 2025
8f4d75a
chore: remove google from invoices
ChrOertlin Feb 17, 2025
202ca6d
chore: various improvements
ChrOertlin Feb 17, 2025
d702276
chore: linting
ChrOertlin Feb 17, 2025
5527ea0
feat: init from config
ChrOertlin Feb 18, 2025
d352cc6
Merge branch 'master' into feat-keycloak
Feb 18, 2025
8de7bb5
chore: merge
ChrOertlin Feb 18, 2025
bbb6cbd
Merge branch 'master' into feat-keycloak
Feb 19, 2025
cd0a2ba
Merge branch 'feat-keycloak' of https://github.com/Clinical-Genomics/…
ChrOertlin Feb 20, 2025
4d6995f
chore:changes
ChrOertlin Feb 20, 2025
589ea5d
feat: add user role check
ChrOertlin Feb 24, 2025
729cde3
feat: fix admin view
ChrOertlin Feb 24, 2025
0316ff4
chore: linitng
ChrOertlin Feb 24, 2025
5dbb483
logging
ChrOertlin Feb 25, 2025
924fd4c
chore: extract client from auth service
ChrOertlin Feb 25, 2025
1eee249
fix: auth init
ChrOertlin Feb 25, 2025
39f3f5d
Merge branch 'master' into feat-keycloak
Feb 28, 2025
6b4233d
chore: simplify flow
ChrOertlin Mar 3, 2025
c1e6ef4
rm: token refresh
ChrOertlin Mar 3, 2025
9673d22
chore: cleanup flows
ChrOertlin Mar 3, 2025
afea95a
fix: access
ChrOertlin Mar 3, 2025
4f4520f
chore: improve error handling auth
ChrOertlin Mar 5, 2025
031e4ba
chore: error handling
ChrOertlin Mar 5, 2025
54a94fb
Merge branch 'master' into feat-keycloak
Mar 5, 2025
ad0b484
chore: linting
ChrOertlin Mar 5, 2025
22ee0ae
Merge branch 'feat-keycloak' of https://github.com/Clinical-Genomics/…
ChrOertlin Mar 5, 2025
a7af4f8
fix: proper token access
ChrOertlin Mar 5, 2025
aeddad6
fix: cache user roles for access
ChrOertlin Mar 6, 2025
848f245
chore: linting
ChrOertlin Mar 6, 2025
49c3b82
fix: various fixes
ChrOertlin Mar 6, 2025
689d54a
fix
ChrOertlin Mar 6, 2025
d04a279
chore: register client and client config in cg config
ChrOertlin Mar 7, 2025
16763b5
chore: add proper instantiation
ChrOertlin Mar 10, 2025
e3c3258
add: general client tests
ChrOertlin Mar 10, 2025
f188269
chore: add tests
ChrOertlin Mar 10, 2025
3b539d8
chore: linting
ChrOertlin Mar 10, 2025
c671af8
Merge branch 'master' into feat-keycloak
Mar 10, 2025
029b14f
Merge branch 'master' into feat-keycloak
islean May 7, 2025
2be972c
fix: potential XSS with markupsafe.Markup
ahdamin May 7, 2025
81f771f
fix: syntax and black formatting
ahdamin May 7, 2025
f94876c
fix: view_case_sample_link issue
ahdamin May 7, 2025
9d852e3
fix: potential XSS issue
ahdamin May 7, 2025
bab7189
fix: potential XSS issue
ahdamin May 7, 2025
3d5b93b
fix: potential XSS issue with Markup
ahdamin May 7, 2025
1945ad1
fix: potential XSS issue with Markup
ahdamin May 7, 2025
d157ead
fix: potential XSS issue with Markup
ahdamin May 7, 2025
8a40328
fix: potential XSS issue with Markup
ahdamin May 7, 2025
a0f633d
fix: potential XSS issue with Markup
ahdamin May 7, 2025
b7ae920
fix: potential XSS issue with Markup
ahdamin May 7, 2025
eec1acf
fix: potential XSS issue with Markup
ahdamin May 7, 2025
61594b6
fix: potential XSS issue with Markup
ahdamin May 7, 2025
5a0ed30
fix: potential XSS issue with Markup
ahdamin May 7, 2025
f2f9754
fix: potential XSS issue with Markup
ahdamin May 7, 2025
12604ca
fix: potential XSS issue with Markup
ahdamin May 7, 2025
a44cc1c
fix: potential XSS issue with Markup
ahdamin May 7, 2025
7277719
fix: potential XSS issue with Markup
ahdamin May 7, 2025
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
4 changes: 4 additions & 0 deletions cg/server/app_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ class AppConfig(BaseSettings):
freshdesk_api_key: str = "freshdesk_api_key"
freshdesk_order_email_id: int = 10
freshdesk_environment: str = "Stage"
keycloak_client_url: str = "http://localhost:8081"
keycloak_realm_name = "orderportal"
keycloak_client_id = "cg-backend-client"
keycloak_client_secret_key = "cg-very-secret-password"


app_config = AppConfig()
38 changes: 18 additions & 20 deletions cg/server/endpoints/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,22 @@
from http import HTTPStatus

import cachecontrol
from keycloak import KeycloakError
import requests
from flask import abort, current_app, g, jsonify, make_response, request
from google.auth import exceptions
from google.auth.transport import requests as google_requests
from google.oauth2 import id_token

from cg.server.ext import db
from cg.store.models import User
from cg.server.ext import auth_service
from cg.services.authentication.models import AuthenticatedUser




LOG = logging.getLogger(__name__)

session = requests.session()
cached_session = cachecontrol.CacheControl(session)


def verify_google_token(token):
request = google_requests.Request(session=cached_session)
return id_token.verify_oauth2_token(id_token=token, request=request)


def is_public(route_function):
@wraps(route_function)
def public_endpoint(*args, **kwargs):
Expand Down Expand Up @@ -54,17 +50,19 @@ def before_request():

jwt_token = auth_header.split("Bearer ")[-1]
try:
user_data = verify_google_token(jwt_token)
except (exceptions.OAuthError, ValueError) as e:
LOG.error(f"Error {e} occurred while decoding JWT token: {jwt_token}")
user: AuthenticatedUser = auth_service.verify_token(jwt_token)

except ValueError as error:
return abort(
make_response(jsonify(message="outdated login certificate"), HTTPStatus.UNAUTHORIZED)
make_response(jsonify(message=str(error)), HTTPStatus.FORBIDDEN)
)
except KeycloakError as error:
return abort(
make_response(jsonify(message=str(error)), HTTPStatus.UNAUTHORIZED)
)
except Exception as error:
return abort(
make_response(jsonify(message=str(error)), HTTPStatus.INTERNAL_SERVER_ERROR)
)

user: User = db.get_user_by_email(user_data["email"])
if user is None or not user.order_portal_login:
message = f"{user_data['email']} doesn't have access"
LOG.error(message)
return abort(make_response(jsonify(message=message), HTTPStatus.FORBIDDEN))

g.current_user = user
10 changes: 10 additions & 0 deletions cg/server/ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
from flask_admin import Admin
from flask_cors import CORS
from flask_wtf.csrf import CSRFProtect
from keycloak import KeycloakOpenID

from cg.apps.lims import LimsAPI
from cg.apps.tb.api import TrailblazerAPI
from cg.clients.freshdesk.freshdesk_client import FreshdeskClient
from cg.server.app_config import app_config
from cg.services.application.service import ApplicationsWebService
from cg.services.authentication.service import AuthenticationService
from cg.services.delivery_message.delivery_message_service import DeliveryMessageService
from cg.services.orders.order_service.order_service import OrderService
from cg.services.orders.order_summary_service.order_summary_service import OrderSummaryService
Expand Down Expand Up @@ -108,3 +110,11 @@ def init_app(self, app):
system_email_id=app_config.freshdesk_order_email_id,
env=app_config.freshdesk_environment,
)

auth_service = AuthenticationService(
store=db,
server_url=app_config.keycloak_client_url,
client_id=app_config.keycloak_client_id,
client_secret=app_config.keycloak_client_secret_key,
realm_name=app_config.keycloak_realm_name,
)
2 changes: 2 additions & 0 deletions cg/services/authentication/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ADMIN: str = "admin"
CUSTOMER: str = "customer"
8 changes: 8 additions & 0 deletions cg/services/authentication/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from pydantic import BaseModel


class AuthenticatedUser(BaseModel):
id: int
username: str
email: str
role: str
110 changes: 110 additions & 0 deletions cg/services/authentication/service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from cycler import V
from cg.services.authentication.constants import ADMIN, CUSTOMER
from cg.store.models import User
from cg.store.store import Store
from keycloak import KeycloakError, KeycloakOpenID
from cg.services.authentication.models import AuthenticatedUser


class AuthenticationService:
"""Authentication service user to verify tokens against keycloak and return user information."""

def __init__(
self, store: Store, server_url: str, client_id: str, client_secret: str, realm_name: str
):
"""_summary_

Args:
store (Store): Connection to statusDB
server_url (str): server url to the keycloak server or container
realm_name (str): the keycloak realm to connect to (can be found in keycloak)
client_id (str): the client id to use in keycloak realm (can be found in keycloak)
client_secret (str): the client secret to use in keycloak realm (can be found in keycloak)
"""
self.store: Store = store
self.server_url: str = server_url
self.client_id: str = client_id
self.client_secret: str = client_secret
self.realm_name: str = realm_name
self.client: KeycloakOpenID = self._get_client()

def _get_client(self):
"""Set the KeycloakOpenID client.
"""
keycloak_openid_client = KeycloakOpenID(
server_url=self.server_url,
client_id=self.client_id,
realm_name=self.realm_name,
client_secret_key=self.client_secret,
)
return keycloak_openid_client

def verify_token(self, token: str) -> AuthenticatedUser:
"""Verify the token and return the user.
args:
token: str
returns:
AuthenticatedUser
raises:
ValueError: if the token is not active
"""
token_info = self.client.introspect(token)

if not token_info['active']:
raise ValueError('Token is not active')
verified_token = self.client.decode_token(token)

user_email = verified_token["email"]
return self._get_user_reponse(user_email)



def _get_user_by_email(self, email: str):
"""
Get user by email.
args:
email: str
returns:
User
raises:
ValueError: if the user is not found
"""
user: User | None = self.store.get_user_by_email(email)
if not user:
raise ValueError(f"User with email {email} not found")
return user

def _check_role(self, user: User) -> str:
"""
Check if user has the role.
args:
user: User
returns:
str
raises:
ValueError: if the user does not have access
"""
if user.is_admin:
return ADMIN
if user.order_portal_login:
return CUSTOMER
raise ValueError('User does not have access')

def _get_user_reponse(self, email: str) -> AuthenticatedUser:
"""
Get user response.
args:
email: str
returns:
AuthenticatedUser
"""
user = self._get_user_by_email(email)
role: str = self._check_role(user)
return AuthenticatedUser(
id=user.id,
username=user.name,
email=user.email,
role=role,
)


Loading
Loading