vincbeck commented on code in PR #59411:
URL: https://github.com/apache/airflow/pull/59411#discussion_r2620105726


##########
providers/keycloak/src/airflow/providers/keycloak/auth_manager/services/token.py:
##########
@@ -51,3 +51,47 @@ def create_token_for(
     )
 
     return get_auth_manager().generate_jwt(user, 
expiration_time_in_seconds=expiration_time_in_seconds)
+
+
+def create_token_for_client_credentials(
+    client_id: str,
+    client_secret: str,
+    expiration_time_in_seconds: int = conf.getint("api_auth", 
"jwt_expiration_time"),
+) -> str:
+    """
+    Create token using OAuth2 client_credentials grant type.
+
+    This authentication flow uses the provided client_id and client_secret
+    to obtain a token for a service account. The Keycloak client must have:
+    - Service accounts roles: ON
+    - Client Authentication: ON (confidential client)
+
+    The service account must be configured with the appropriate 
roles/permissions.
+    """
+    # Get Keycloak client with service account credentials
+    client = KeycloakAuthManager.get_keycloak_client(
+        client_id=client_id,
+        client_secret=client_secret,
+    )
+
+    try:
+        tokens = client.token(grant_type="client_credentials")
+    except KeycloakAuthenticationError:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail="Client credentials authentication failed",
+        )
+
+    # For client_credentials, get the service account user info
+    # The token represents the service account associated with the client
+    userinfo = client.userinfo(tokens["access_token"])
+    user = KeycloakAuthManagerUser(
+        user_id=userinfo["sub"],
+        name=userinfo.get("preferred_username", userinfo.get("clientId", 
"service-account")),
+        access_token=tokens["access_token"],
+        refresh_token=tokens.get(

Review Comment:
   Instead of having an empty string, I rather update `KeycloakAuthManagerUser` 
and update the type of `refresh_token` to be `str | None`. 



##########
providers/keycloak/src/airflow/providers/keycloak/auth_manager/keycloak_auth_manager.py:
##########
@@ -310,9 +317,22 @@ def get_cli_commands() -> list[CLICommand]:
         ]
 
     @staticmethod
-    def get_keycloak_client() -> KeycloakOpenID:
-        client_id = conf.get(CONF_SECTION_NAME, CONF_CLIENT_ID_KEY)
-        client_secret = conf.get(CONF_SECTION_NAME, CONF_CLIENT_SECRET_KEY)
+    def get_keycloak_client(client_id: str | None = None, client_secret: str | 
None = None) -> KeycloakOpenID:
+        """
+        Get a KeycloakOpenID client instance.
+
+        :param client_id: Optional client ID to override config. If provided, 
client_secret must also be provided.
+        :param client_secret: Optional client secret to override config. If 
provided, client_id must also be provided.
+        """
+        if (client_id is None) != (client_secret is None):
+            raise ValueError(
+                "Both client_id and client_secret must be provided together, 
or both must be None"

Review Comment:
   ```suggestion
                   "Both `client_id` and `client_secret` must be provided 
together, or both must be None"
   ```



##########
providers/keycloak/src/airflow/providers/keycloak/auth_manager/services/token.py:
##########
@@ -51,3 +51,47 @@ def create_token_for(
     )
 
     return get_auth_manager().generate_jwt(user, 
expiration_time_in_seconds=expiration_time_in_seconds)
+
+
+def create_token_for_client_credentials(

Review Comment:
   Reads better (to me)
   
   ```suggestion
   def create_client_credentials_token(
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to