Skip to content
This repository was archived by the owner on Mar 16, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 17 additions & 3 deletions fastapi_keycloak/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from urllib.parse import urlencode

import requests
from fastapi import Depends, FastAPI, HTTPException
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import ExpiredSignatureError, JWTError, jwt
from jose.exceptions import JWTClaimsError
Expand Down Expand Up @@ -221,12 +221,13 @@ def user_auth_scheme(self) -> OAuth2PasswordBearer:
"""
return OAuth2PasswordBearer(tokenUrl=self.token_uri)

def get_current_user(self, required_roles: List[str] = None) -> OIDCUser:
def get_current_user(self, required_roles: List[str] = None, extra_fields: List[str] = None) -> OIDCUser:
"""Returns the current user based on an access token in the HTTP-header. Optionally verifies roles are possessed
by the user

Args:
required_roles List[str]: List of role names required for this endpoint
extra_fields List[str]: The names of the additional fields you need that are encoded in JWT

Returns:
OIDCUser: Decoded JWT content
Expand Down Expand Up @@ -258,12 +259,22 @@ def current_user(
decoded_token = self._decode_token(token=token, audience="account")
user = OIDCUser.parse_obj(decoded_token)
if required_roles:
if not user.realm_access: # in cases where there are no roles in realm accessing
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"Role(s) {', '.join(required_roles)} is required to perform this action",
)
for role in required_roles:
if role not in user.roles:
raise HTTPException(
status_code=403,
status_code=status.HTTP_403_FORBIDDEN,
detail=f'Role "{role}" is required to perform this action',
)

if extra_fields:
for field in extra_fields:
user.extra_fields[field] = decoded_token.get(field, None)

return user

return current_user
Expand Down Expand Up @@ -715,6 +726,7 @@ def create_user(
enabled: bool = True,
initial_roles: List[str] = None,
send_email_verification: bool = True,
attributes: dict[str, Any] = None,
) -> KeycloakUser:
"""

Expand All @@ -729,6 +741,7 @@ def create_user(
send_email_verification (bool): If true, the email verification will be added as an required
action and the email triggered - if the user was created successfully.
Defaults to `True`
attributes (dict): attributes of new user

Returns:
KeycloakUser: If the creation succeeded
Expand All @@ -749,6 +762,7 @@ def create_user(
{"temporary": False, "type": "password", "value": password}
],
"requiredActions": ["VERIFY_EMAIL" if send_email_verification else None],
"attributes": attributes,
}
response = self._admin_request(
url=self.users_uri, data=data, method=HTTPMethod.POST
Expand Down
2 changes: 2 additions & 0 deletions fastapi_keycloak/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class OIDCUser(BaseModel):
preferred_username (Optional[str]):
realm_access (dict):
resource_access (dict):
extra_fields (dict):

Notes: Check the Keycloak documentation at https://www.keycloak.org/docs-api/15.0/rest-api/index.html for
details. This is a mere proxy object.
Expand All @@ -108,6 +109,7 @@ class OIDCUser(BaseModel):
preferred_username: Optional[str]
realm_access: Optional[dict]
resource_access: Optional[dict]
extra_fields: Optional[dict]

@property
def roles(self) -> List[str]:
Expand Down