Skip to content

Commit

Permalink
AAP integration
Browse files Browse the repository at this point in the history
issue: https://issues.redhat.com/browse/AAP-39611
Signed-off-by: Djebran Lezzoum <ldjebran@gmail.com>
  • Loading branch information
ldjebran committed Feb 24, 2025
1 parent b64b0b9 commit 218ed7e
Show file tree
Hide file tree
Showing 20 changed files with 343 additions and 20 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,6 @@ chatbot-test:
.PHONY: chatbot-lint
chatbot-lint:
npm --prefix ./ansible_ai_connect_chatbot run eslint

pre-commit:
pre-commit run --color=always --all-files
22 changes: 22 additions & 0 deletions ansible_ai_connect/ai/api/openapi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright Red Hat
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


def preprocessing_filter_spec(endpoints):
filtered = []
for path, path_regex, method, callback in endpoints:
# do not add internal endpoints to schema
if not path.startswith("/api/v1/service-index"):
filtered.append((path, path_regex, method, callback))
return filtered
3 changes: 2 additions & 1 deletion ansible_ai_connect/ai/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from ansible_base.resource_registry.urls import urlpatterns as resource_api_urls
from django.urls import include, path

from .versions.v0 import urls as v0_urls
Expand All @@ -20,4 +20,5 @@
urlpatterns = [
path("v0/", include((v0_urls, "ai"), namespace="v0")),
path("v1/", include((v1_urls, "ai"), namespace="v1")),
path("v1/", include(resource_api_urls)),
]
7 changes: 7 additions & 0 deletions ansible_ai_connect/ai/api/versions/v1/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@

from django.urls import include, path

from ansible_ai_connect.healthcheck.views import (
WisdomServiceHealthView,
WisdomServiceLivenessProbeView,
)

from .ai import urls as ai_urls
from .telemetry import urls as telemetry_urls
from .users import urls as me_urls
Expand All @@ -24,4 +29,6 @@
path("me/", include(me_urls)),
path("telemetry/", include(telemetry_urls)),
path("wca/", include(wca_urls)),
path("health/", WisdomServiceLivenessProbeView.as_view(), name="health"),
path("health/status/", WisdomServiceHealthView.as_view(), name="health_status"),
]
4 changes: 2 additions & 2 deletions ansible_ai_connect/ai/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
)
from ansible_ai_connect.users.models import User

from ...main.permissions import IsRHInternalUser, IsTestUser
from ...main.permissions import IsAAPUser, IsRHInternalUser, IsTestUser
from ...users.throttling import EndpointRateThrottle
from ..feature_flags import FeatureFlags
from .data.data_model import ContentMatchPayloadData, ContentMatchResponseDto
Expand Down Expand Up @@ -1041,7 +1041,7 @@ class ChatEndpointThrottle(EndpointRateThrottle):
permission_classes = [
permissions.IsAuthenticated,
IsAuthenticatedOrTokenHasScope,
IsRHInternalUser | IsTestUser,
IsRHInternalUser | IsTestUser | IsAAPUser,
]
required_scopes = ["read", "write"]
schema1_event = schema1.ChatBotOperationalEvent
Expand Down
35 changes: 35 additions & 0 deletions ansible_ai_connect/ai/resource_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from ansible_base.resource_registry.registry import (
ResourceConfig,
ServiceAPIConfig,
SharedResource,
)
from ansible_base.resource_registry.shared_types import UserType
from ansible_base.resource_registry.utils.resource_type_processor import (
ResourceTypeProcessor,
)
from django.contrib.auth import get_user_model


class UserProcessor(ResourceTypeProcessor):
def pre_serialize_additional(self):
# These fields aren't supported in app, so we'll set them to blank
setattr(self.instance, "external_auth_provider", None)
setattr(self.instance, "external_auth_uid", None)
setattr(self.instance, "organizations", [])
setattr(self.instance, "organizations_administered", [])

return self.instance


class APIConfig(ServiceAPIConfig):
custom_resource_processors = {"shared.user": UserProcessor}
service_type = "lightspeed"


RESOURCE_LIST = [
ResourceConfig(
get_user_model(),
shared_resource=SharedResource(serializer=UserType, is_provider=False),
name_field="username",
),
]
13 changes: 13 additions & 0 deletions ansible_ai_connect/main/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,16 @@ class IsTestUser(BasePermission):
def has_permission(self, request, view):
user = request.user
return user.is_authenticated and user.groups.filter(name="test").exists()


class IsAAPUser(BasePermission):
"""
Allow access only to authenticated AAP users
"""

code = "permission_denied_user_not_from_aap"
message = "The user is not an AAP User"

def has_permission(self, request, view):
user = request.user
return user.is_authenticated and user.aap_user
6 changes: 6 additions & 0 deletions ansible_ai_connect/main/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@
"ansible_ai_connect.healthcheck",
"oauth2_provider",
"import_export",
"ansible_base.resource_registry",
"ansible_base.jwt_consumer",
]

MIDDLEWARE = [
Expand Down Expand Up @@ -262,6 +264,7 @@ def is_ssl_enabled(value: str) -> bool:
"DEFAULT_AUTHENTICATION_CLASSES": [
"oauth2_provider.contrib.rest_framework.OAuth2Authentication",
"rest_framework.authentication.SessionAuthentication",
"ansible_ai_connect.users.authentication.LightspeedJWTAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"],
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
Expand All @@ -276,6 +279,9 @@ def is_ssl_enabled(value: str) -> bool:

API_VERSION = "1.0.0"

ANSIBLE_BASE_ORGANIZATION_MODEL = "ansible_ai_connect.organizations.models.Organization"
ANSIBLE_BASE_RESOURCE_CONFIG_MODULE = "ansible_ai_connect.ai.resource_api"

# Current RHSSOAuthentication implementation is incompatible with tech preview terms partial
if not ANSIBLE_AI_ENABLE_TECH_PREVIEW:
REST_FRAMEWORK["DEFAULT_AUTHENTICATION_CLASSES"].insert(
Expand Down
1 change: 1 addition & 0 deletions ansible_ai_connect/main/settings/development.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
{"name": "wca", "description": "watsonx Code Assistant"},
],
"SCHEMA_PATH_PREFIX": r"/api/v[0-9]+",
"PREPROCESSING_HOOKS": ["ansible_ai_connect.ai.api.openapi.preprocessing_filter_spec"],
}

# social_django does not process auth exceptions when DEBUG=True by default.
Expand Down
4 changes: 2 additions & 2 deletions ansible_ai_connect/main/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
IsOrganisationLightspeedSubscriber,
)
from ansible_ai_connect.main.base_views import ProtectedTemplateView
from ansible_ai_connect.main.permissions import IsRHInternalUser, IsTestUser
from ansible_ai_connect.main.permissions import IsAAPUser, IsRHInternalUser, IsTestUser
from ansible_ai_connect.main.settings.base import SOCIAL_AUTH_OIDC_KEY

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -118,7 +118,7 @@ class ChatbotView(ProtectedTemplateView):
template_name = "chatbot/index.html"

permission_classes = [
IsRHInternalUser | IsTestUser,
IsRHInternalUser | IsTestUser | IsAAPUser,
]

llm: ModelPipelineChatBot
Expand Down
12 changes: 12 additions & 0 deletions ansible_ai_connect/users/authentication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from ansible_base.jwt_consumer.common.auth import JWTAuthentication


class LightspeedJWTAuthentication(JWTAuthentication):

def authenticate(self, request):
userdata = super().authenticate(request)
if userdata:
user, _ = userdata
user.aap_user = True
user.save()
return userdata
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Generated by Django 4.2.18 on 2025-02-11 09:03

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("users", "0014_user_rh_internal_user"),
]

operations = [
migrations.AlterField(
model_name="user",
name="email",
field=models.CharField(default=None, max_length=150, null=True),
),
migrations.AlterField(
model_name="user",
name="external_username",
field=models.CharField(default="", max_length=150),
),
migrations.AlterField(
model_name="user",
name="family_name",
field=models.CharField(default=None, max_length=150, null=True),
),
migrations.AlterField(
model_name="user",
name="given_name",
field=models.CharField(default=None, max_length=150, null=True),
),
migrations.AlterField(
model_name="user",
name="name",
field=models.CharField(default=None, max_length=150, null=True),
),
]
18 changes: 18 additions & 0 deletions ansible_ai_connect/users/migrations/0016_user_aap_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.18 on 2025-02-17 08:27

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("users", "0015_alter_user_email_alter_user_external_username_and_more"),
]

operations = [
migrations.AddField(
model_name="user",
name="aap_user",
field=models.BooleanField(default=False),
),
]
11 changes: 6 additions & 5 deletions ansible_ai_connect/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,13 @@ class User(ExportModelOperationsMixin("user"), AbstractUser):
)
rh_user_is_org_admin = models.BooleanField(default=False)
rh_internal = models.BooleanField(default=False)
external_username = models.CharField(default="", null=False)
name = models.CharField(default=None, null=True)
given_name = models.CharField(default=None, null=True)
family_name = models.CharField(default=None, null=True)
email = models.CharField(default=None, null=True)
external_username = models.CharField(default="", null=False, max_length=150)
name = models.CharField(default=None, null=True, max_length=150)
given_name = models.CharField(default=None, null=True, max_length=150)
family_name = models.CharField(default=None, null=True, max_length=150)
email = models.CharField(default=None, null=True, max_length=150)
email_verified = models.BooleanField(default=False, null=True)
aap_user = models.BooleanField(default=False)

@property
def org_id(self) -> int | None:
Expand Down
24 changes: 21 additions & 3 deletions requirements-aarch64.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ anyio==4.6.2.post1
argparse==1.4.0
# via uwsgi-readiness-check
asgiref==3.8.1
# via django
# via
# django
# django-ansible-base
asttokens==2.4.1
# via stack-data
attrs==23.2.0
Expand Down Expand Up @@ -72,6 +74,7 @@ cryptography==43.0.1
# via
# -r requirements.in
# ansible-core
# django-ansible-base
# jwcrypto
# pyopenssl
# social-auth-core
Expand All @@ -88,6 +91,8 @@ django==4.2.18
# via
# -r requirements.in
# django-allow-cidr
# django-ansible-base
# django-crum
# django-csp
# django-deprecate-fields
# django-extensions
Expand All @@ -99,6 +104,10 @@ django==4.2.18
# social-auth-app-django
django-allow-cidr==0.6.0
# via -r requirements.in
django-ansible-base[jwt-consumer,resource-registry] @ git+https://github.com/ansible/django-ansible-base@devel
# via -r requirements.in
django-crum==0.7.9
# via django-ansible-base
django-csp==3.7
# via -r requirements.in
django-deprecate-fields==0.1.1
Expand All @@ -113,11 +122,14 @@ django-oauth-toolkit==2.3.0
# via -r requirements.in
django-prometheus==2.2.0
# via -r requirements.in
django-split-settings==1.3.2
# via django-ansible-base
django-test-migrations==1.3.0
# via -r requirements.in
djangorestframework==3.15.2
# via
# -r requirements.in
# django-ansible-base
# drf-spectacular
drf-spectacular==0.27.2
# via -r requirements.in
Expand Down Expand Up @@ -175,7 +187,9 @@ idna==3.7
# requests
# yarl
inflection==0.5.1
# via drf-spectacular
# via
# django-ansible-base
# drf-spectacular
ipython==8.10.0
# via -r requirements.in
jedi==0.19.1
Expand Down Expand Up @@ -331,6 +345,7 @@ pygments==2.17.2
pyjwt==2.8.0
# via
# -r requirements.in
# django-ansible-base
# social-auth-core
pyopenssl==24.2.1
# via
Expand Down Expand Up @@ -373,6 +388,7 @@ requests==2.32.0
# via
# -r requirements.in
# ansible-risk-insight
# django-ansible-base
# django-oauth-toolkit
# google-api-core
# langchain
Expand Down Expand Up @@ -433,10 +449,11 @@ social-auth-core==4.5.4
# social-auth-app-django
sqlalchemy==2.0.29
# via langchain
sqlparse==0.5.0
sqlparse==0.5.2
# via
# -r requirements.in
# django
# django-ansible-base
stack-data==0.6.3
# via ipython
subprocess-tee==0.4.1
Expand Down Expand Up @@ -472,6 +489,7 @@ urllib3==1.26.19
# via
# -r requirements.in
# botocore
# django-ansible-base
# launchdarkly-server-sdk
# requests
uwsgi==2.0.22
Expand Down
Loading

0 comments on commit 218ed7e

Please sign in to comment.