Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Lightspeed Core Service (LCS) - RH Identity Auth
service:
host: 0.0.0.0
port: 8080
auth_enabled: true
workers: 1
color_log: true
access_log: true
llama_stack:
use_as_library_client: true
library_client_config_path: run.yaml
user_data_collection:
feedback_enabled: true
feedback_storage: "/tmp/data/feedback"
transcripts_enabled: true
transcripts_storage: "/tmp/data/transcripts"
conversation_cache:
type: "sqlite"
sqlite:
db_path: "/tmp/data/conversation-cache.db"
authentication:
module: "rh-identity"
rh_identity_config:
required_entitlements: ["rhel"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Lightspeed Core Service (LCS) - RH Identity Auth
service:
host: 0.0.0.0
port: 8080
auth_enabled: true
workers: 1
color_log: true
access_log: true
llama_stack:
use_as_library_client: false
url: http://llama-stack:8321
api_key: xyzzy
user_data_collection:
feedback_enabled: true
feedback_storage: "/tmp/data/feedback"
transcripts_enabled: true
transcripts_storage: "/tmp/data/transcripts"
conversation_cache:
type: "sqlite"
sqlite:
db_path: "/tmp/data/conversation-cache.db"
authentication:
module: "rh-identity"
rh_identity_config:
required_entitlements: ["rhel"]
163 changes: 163 additions & 0 deletions tests/e2e/features/authorized_rh_identity.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
@RHIdentity
Feature: Authorized endpoint API tests for the rh-identity authentication module

Background:
Given The service is started locally
And REST API service prefix is /v1

Scenario: Request fails when x-rh-identity header is missing
Given The system is in default state
When I access endpoint "authorized" using HTTP POST method
"""
{"placeholder":"abc"}
"""
Then The status code of the response is 401
And The body of the response is the following
"""
{"detail": "Missing x-rh-identity header"}
"""

Scenario: Request fails when identity field is missing
Given The system is in default state
And I set the x-rh-identity header with JSON
"""
{"entitlements": {"rhel": {"is_entitled": true}}}
"""
When I access endpoint "authorized" using HTTP POST method
"""
{"placeholder":"abc"}
"""
Then The status code of the response is 400
And The body of the response contains Missing 'identity' field

Scenario: Request succeeds with valid User identity and required entitlements
Given The system is in default state
And I set the x-rh-identity header with valid User identity
| field | value |
| user_id | test-user-123 |
| username | testuser@redhat.com |
| org_id | 321 |
| entitlements | rhel |
When I access endpoint "authorized" using HTTP POST method
"""
{"placeholder":"abc"}
"""
Then The status code of the response is 200
And The body of the response is the following
"""
{"user_id": "test-user-123", "username": "testuser@redhat.com", "skip_userid_check": false}
"""

Scenario: Request succeeds with valid System identity and required entitlements
Given The system is in default state
And I set the x-rh-identity header with valid System identity
| field | value |
| cn | c87dcb4c-8af1-40dd-878e-60c744edddd0 |
| account_number | 456 |
| org_id | 654 |
| entitlements | rhel |
When I access endpoint "authorized" using HTTP POST method
"""
{"placeholder":"abc"}
"""
Then The status code of the response is 200
And The body of the response is the following
"""
{"user_id": "c87dcb4c-8af1-40dd-878e-60c744edddd0", "username": "456", "skip_userid_check": false}
"""

Scenario: Request fails when required entitlement is missing
Given The system is in default state
And I set the x-rh-identity header with valid User identity
| field | value |
| user_id | test-user-123 |
| username | testuser@redhat.com |
| org_id | 321 |
| entitlements | ansible |
When I access endpoint "authorized" using HTTP POST method
"""
{"placeholder":"abc"}
"""
Then The status code of the response is 403
And The body of the response contains Missing required entitlement

Scenario: Request fails when entitlement exists but is_entitled is false
Given The system is in default state
And I set the x-rh-identity header with JSON
"""
{
"identity": {
"type": "User",
"org_id": "321",
"user": {"user_id": "test-user-123", "username": "testuser@redhat.com"}
},
"entitlements": {"rhel": {"is_entitled": false, "is_trial": true}}
}
"""
When I access endpoint "authorized" using HTTP POST method
"""
{"placeholder":"abc"}
"""
Then The status code of the response is 403
And The body of the response contains Missing required entitlement

Scenario: Request fails when User identity is missing user_id
Given The system is in default state
And I set the x-rh-identity header with JSON
"""
{
"identity": {
"type": "User",
"org_id": "321",
"user": {"username": "testuser@redhat.com"}
},
"entitlements": {"rhel": {"is_entitled": true}}
}
"""
When I access endpoint "authorized" using HTTP POST method
"""
{"placeholder":"abc"}
"""
Then The status code of the response is 400
And The body of the response contains Missing 'user_id' in user data

Scenario: Request fails when User identity is missing username
Given The system is in default state
And I set the x-rh-identity header with JSON
"""
{
"identity": {
"type": "User",
"org_id": "321",
"user": {"user_id": "test-user-123"}
},
"entitlements": {"rhel": {"is_entitled": true}}
}
"""
When I access endpoint "authorized" using HTTP POST method
"""
{"placeholder":"abc"}
"""
Then The status code of the response is 400
And The body of the response contains Missing 'username' in user data

Scenario: Request fails when System identity is missing cn
Given The system is in default state
And I set the x-rh-identity header with JSON
"""
{
"identity": {
"type": "System",
"account_number": "456",
"org_id": "654",
"system": {}
},
"entitlements": {"rhel": {"is_entitled": true}}
}
"""
When I access endpoint "authorized" using HTTP POST method
"""
{"placeholder":"abc"}
"""
Then The status code of the response is 400
And The body of the response contains Missing 'cn' in system data
14 changes: 14 additions & 0 deletions tests/e2e/features/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,15 @@ def before_feature(context: Context, feature: Feature) -> None:
switch_config(context.feature_config)
restart_container("lightspeed-stack")

if "RHIdentity" in feature.tags:
mode_dir = "library-mode" if context.is_library_mode else "server-mode"
context.feature_config = (
f"tests/e2e/configuration/{mode_dir}/lightspeed-stack-auth-rh-identity.yaml"
)
context.default_config_backup = create_config_backup("lightspeed-stack.yaml")
switch_config(context.feature_config)
restart_container("lightspeed-stack")

if "Feedback" in feature.tags:
context.hostname = os.getenv("E2E_LSC_HOSTNAME", "localhost")
context.port = os.getenv("E2E_LSC_PORT", "8080")
Expand All @@ -273,6 +282,11 @@ def after_feature(context: Context, feature: Feature) -> None:
restart_container("lightspeed-stack")
remove_config_backup(context.default_config_backup)

if "RHIdentity" in feature.tags:
switch_config(context.default_config_backup)
restart_container("lightspeed-stack")
remove_config_backup(context.default_config_backup)

if "Feedback" in feature.tags:
for conversation_id in context.feedback_conversations:
url = f"http://{context.hostname}:{context.port}/v1/conversations/{conversation_id}"
Expand Down
116 changes: 116 additions & 0 deletions tests/e2e/features/steps/auth.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
"""Implementation of common test steps."""

import base64
import json

import requests
from behave import given, when # pyright: ignore[reportAttributeAccessIssue]
from behave.runner import Context
from tests.e2e.utils.utils import normalize_endpoint


def _encode_rh_identity(identity_data: dict) -> str:
"""Encode identity dict to base64 for x-rh-identity header.

Args:
identity_data: JSON-serializable identity payload to encode.

Returns:
Base64-encoded UTF-8 string representation of the JSON payload.
"""
json_str = json.dumps(identity_data)
return base64.b64encode(json_str.encode("utf-8")).decode("utf-8")


@given("I set the Authorization header to {header_value}")
def set_authorization_header_custom(context: Context, header_value: str) -> None:
"""Set a custom Authorization header value.
Expand Down Expand Up @@ -73,3 +89,103 @@ def access_rest_api_endpoint_post_without_param(
context.response = requests.post(
url, json="", headers=context.auth_headers, timeout=10
)


@given('I set the x-rh-identity header to raw value "{header_value}"')
def set_rh_identity_header_raw(context: Context, header_value: str) -> None:
"""Set x-rh-identity header with a raw string value for testing invalid base64."""
if not hasattr(context, "auth_headers"):
context.auth_headers = {}
context.auth_headers["x-rh-identity"] = header_value
print(f"Set x-rh-identity header to raw value: {header_value[:50]}...")


@given('I set the x-rh-identity header with base64 encoded value "{raw_value}"')
def set_rh_identity_header_base64_raw(context: Context, raw_value: str) -> None:
"""Set x-rh-identity header with base64-encoded raw string for testing invalid JSON."""
if not hasattr(context, "auth_headers"):
context.auth_headers = {}
encoded = base64.b64encode(raw_value.encode("utf-8")).decode("utf-8")
context.auth_headers["x-rh-identity"] = encoded
print(f"Set x-rh-identity header with base64-encoded: {raw_value}")


@given("I set the x-rh-identity header with JSON")
def set_rh_identity_header_json(context: Context) -> None:
"""Set x-rh-identity header with base64-encoded JSON from context.text."""
if not hasattr(context, "auth_headers"):
context.auth_headers = {}
assert context.text is not None, "JSON payload required"
identity_data = json.loads(context.text)
context.auth_headers["x-rh-identity"] = _encode_rh_identity(identity_data)
print(f"Set x-rh-identity header with JSON: {identity_data}")


@given("I set the x-rh-identity header with valid User identity")
def set_rh_identity_user(context: Context) -> None:
"""Set x-rh-identity header with User identity from table."""
if not hasattr(context, "auth_headers"):
context.auth_headers = {}

assert context.table is not None, "Table with identity fields required"

fields = {row["field"]: row["value"] for row in context.table}

entitlements = {}
if "entitlements" in fields:
for ent in fields["entitlements"].split(","):
ent = ent.strip()
if not ent:
continue
entitlements[ent] = {"is_entitled": True, "is_trial": False}

identity_data = {
"identity": {
"account_number": fields.get("account_number", "123"),
"org_id": fields.get("org_id", "321"),
"type": "User",
"user": {
"user_id": fields.get("user_id", "test-user"),
"username": fields.get("username", "test@redhat.com"),
"is_org_admin": fields.get("is_org_admin", "false").lower() == "true",
},
},
"entitlements": entitlements,
}

context.auth_headers["x-rh-identity"] = _encode_rh_identity(identity_data)
print(f"Set x-rh-identity header with User identity: {fields.get('user_id')}")


@given("I set the x-rh-identity header with valid System identity")
def set_rh_identity_system(context: Context) -> None:
"""Set x-rh-identity header with System identity from table."""
if not hasattr(context, "auth_headers"):
context.auth_headers = {}

assert context.table is not None, "Table with identity fields required"

fields = {row["field"]: row["value"] for row in context.table}

entitlements = {}
if "entitlements" in fields:
for ent in fields["entitlements"].split(","):
ent = ent.strip()
if not ent:
continue
entitlements[ent] = {"is_entitled": True, "is_trial": False}

identity_data = {
"identity": {
"account_number": fields.get("account_number", "123"),
"org_id": fields.get("org_id", "321"),
"type": "System",
"system": {
"cn": fields.get("cn", "default-cn-uuid"),
},
},
"entitlements": entitlements,
}

context.auth_headers["x-rh-identity"] = _encode_rh_identity(identity_data)
print(f"Set x-rh-identity header with System identity: {fields.get('cn')}")
1 change: 1 addition & 0 deletions tests/e2e/test_list.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ features/faiss.feature
features/smoketests.feature
features/authorized_noop.feature
features/authorized_noop_token.feature
features/authorized_rh_identity.feature
features/rbac.feature
features/conversations.feature
features/conversation_cache_v2.feature
Expand Down
Loading