Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5bfaa69
Feature/database (#8)
jorge-romero Mar 11, 2026
b943167
Add project service module with key generation and existence checks (#7)
angelmp01 Mar 12, 2026
8f7c7fa
Chore/project service (#9)
angelmp01 Mar 18, 2026
f7ce0c5
Add api-project-component-v0 module. (#10)
valituguran Mar 23, 2026
1726695
Persistence improvement (#12)
jorge-romero Mar 26, 2026
d2bd7be
Feature/add project request validation and exception handling (#11)
angelmp01 Mar 27, 2026
c273bbe
Feature/create project v0 (#14)
angelmp01 Mar 31, 2026
a0a4d2f
Add mocks for create/get component (#13)
valituguran Mar 31, 2026
28b7333
Feature/security (#15)
jorge-romero Apr 1, 2026
aee0c8f
Update LDAP group pattern property in ProjectServiceImpl and add info…
jorge-romero Apr 7, 2026
60c5067
Refactor component creation and retrieval logic; enhance error handli…
angelmp01 Apr 21, 2026
bcbbb11
Add ProjectAlreadyExistsException and enhance project creation valida…
angelmp01 Apr 27, 2026
d619cd1
Project registration without automation execution (#21)
Joselee2908 Apr 30, 2026
44befbb
Add marketplace 2.0 api client (#18)
valituguran May 5, 2026
f7c9aae
Feature/marketplace deletion (#23)
valituguran May 7, 2026
463b5ca
Encode password value as base64 (#25)
valituguran May 14, 2026
83d4493
Add missing API imports to database and refactor certificate installa…
jorge-romero May 15, 2026
d91c12a
Feature/register component with registration flag (#29)
Joselee2908 May 19, 2026
b95a7f3
Implement reserved parameter validation for project component creatio…
angelmp01 May 25, 2026
e875897
Fix/project registration checks if already exists (#26)
Joselee2908 May 25, 2026
424df55
Fixed Bitbucket URL format on component registration (#32)
Joselee2908 May 26, 2026
07c31ed
Use project description from request when creating project entity (#31)
angelmp01 May 27, 2026
8d44656
Fix project key validation regex to enforce correct format (#33)
angelmp01 Jun 4, 2026
6df52cd
Updated GetComponent marketplace endpoint call (#34)
Joselee2908 Jun 10, 2026
8669f78
Add audience extraction and validation methods for JWT tokens and Ref…
angelmp01 Jun 23, 2026
de88c03
Fix typo in httpStatus field in API schema and response mapper (#38)
angelmp01 Jun 23, 2026
8366412
Feature/update project status endpoint (#39)
Joselee2908 Jun 23, 2026
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
17 changes: 15 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ dependency-reduced-pom.xml
*.swp
*.bak
*.tmp

# Persistence module — local database credentials (never commit real passwords)
persistence/liquibase.properties
persistence/.env

# Legacy: Database module references (migrated to persistence/)
database/liquibase.properties
database/.env
*.orig
*.rej

Expand Down Expand Up @@ -85,6 +93,11 @@ api-project-users/src/main/java/org/opendevstack/apiservice/projectusers/api
api-project-users/src/main/java/org/opendevstack/apiservice/projectusers/model
api-project-platform/src/main/java/org/opendevstack/apiservice/projectplatform/api
api-project-platform/src/main/java/org/opendevstack/apiservice/projectplatform/model
api-project-users/.openapi-generator
/api-project-platform/.openapi-generator/
api-project/src/main/java/org/opendevstack/apiservice/project/api
api-project/src/main/java/org/opendevstack/apiservice/project/model
api-project-component-v0/src/main/java/org/opendevstack/apiservice/project/api
api-project-component-v0/src/main/java/org/opendevstack/apiservice/project/model
external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/openapi

**/.openapi-generator

24 changes: 23 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,30 @@ All notable changes to the DevStack API Service project will be documented in th
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

#### Security (`core-security`)
- Support for **multiple JWT issuers**: each trusted issuer pairs an issuer URI (`iss` claim) with a JWK set URI, selected by the incoming token's `iss` claim (`OAUTH2_ISSUER_V1`/`OAUTH2_JWK_SET_URI_V1`, `OAUTH2_ISSUER_V2`/`OAUTH2_JWK_SET_URI_V2`).
- Shared **audience validation** across all configured issuers (`OAUTH2_AUDIENCE`, `OAUTH2_AUDIENCE2`); a valid token must carry at least one accepted `aud` value.
- `JwtUtils` audience and scope extraction helpers, including `tokenMatchesScopeAudience` to detect tokens that already target a given audience and scope.

#### External Service Marketplace (`external-service-marketplace`)
- **OBO exchange bypass**: when the incoming token already targets the configured bypass audience and scope, the token is forwarded as-is instead of performing an On-Behalf-Of exchange (`MARKETPLACE_BYPASS_AUDIENCE`, `MARKETPLACE_BYPASS_SCOPE`).

### Changed
- Refactored `SecurityConfig` to resolve the authentication manager per issuer using `JwtIssuerAuthenticationManagerResolver`.
- Moved OAuth2 resource server configuration from Spring's `spring.security.oauth2.resourceserver` properties to application-managed `app` security properties.
- `ClientCredentialFlowValidator` now accepts the client identifier from either the `appid` (v1 tokens) or `azp` (v2 tokens) claim.

## [0.0.3] - 2026-03-03

### Added

- Created **New API module** for managing EDP Projects with create and retrieve endpoints.
- Created **New module** external service projects to manage EDP Projects.

### External Service Jira (`external-service-jira`)
- **New module** for checking project existance in Jira (Server)
- Caching for the client
Expand All @@ -22,6 +42,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
#### Health Monitoring & Observability
- **Spring Boot Actuator** health indicators for all external services:

#### Persistance to keep project lifecycle

### Changed
- Updated all external service implementations to extend `ExternalService` interface
- Use of lombok.extern.slf4j.Slf4j to remove boilerplate code.
Expand All @@ -32,7 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Updated Spring Boot version
- Added health indicator dependencies across external service modules

## [0.0.2] - 2026-03-03
## [0.0.3] - 2026-03-03

### Changed
##### Projects Info Service
Expand Down
163 changes: 160 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# Supports building Spring Boot JAR and Native applications

# Project variables
PROJECT_NAME := devstack-api-service
VERSION := 0.0.2
PROJECT_NAME := opendevstack-api-service
VERSION := 0.0.3
JAVA_VERSION := 21
MAIN_CLASS := org.opendevstack.apiservice.core.DevstackApiServiceApplication

Expand Down Expand Up @@ -44,7 +44,8 @@ YELLOW := \033[1;33m
BLUE := \033[0;34m
NC := \033[0m # No Color

.PHONY: help clean compile test package jar native docker docker-native run-jar run-native run-docker run-docker-native install verify lint format check-java check-maven check-config
.PHONY: help clean compile test package jar native docker docker-native run-jar run-native run-docker run-docker-native install verify lint format check-java check-maven check-config \
db-check-env db-validate db-status db-migrate db-rollback db-tag db-port-forward db-docker-build-db

# Default target
.DEFAULT_GOAL := help
Expand Down Expand Up @@ -90,8 +91,25 @@ check-maven:
clean: check-maven
@echo "$(BLUE)Cleaning build artifacts...$(NC)"
$(MAVEN_WRAPPER) clean
@echo "$(BLUE)Removing all target directories across modules...$(NC)"
@find . -type d -name target -prune -exec rm -rf {} +
@echo "$(BLUE)Removing generated OpenAPI model directories...$(NC)"
@find . -type d -path "*/src/main/java/*/model" \
! -path "./external-service-aap/src/main/java/org/opendevstack/apiservice/externalservice/aap/model" \
! -path "./service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model" \
! -path "./external-service-projects-info-service/src/main/java/org/opendevstack/apiservice/externalservice/projectsinfoservice/model" \
! -path "./external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/model" \
! -path "./external-service-uipath/src/main/java/org/opendevstack/apiservice/externalservice/uipath/model" \
-prune -exec rm -rf {} +
@echo "$(GREEN)✓ Clean complete$(NC)"

## Remove local Maven cache for org.opendevstack.apiservice
clean-cache:
@echo "$(BLUE)Removing local Maven cache for org.opendevstack.apiservice...$(NC)"
@rm -rf "$$HOME/.m2/repository/org/opendevstack/apiservice"
@rm -rf "$$HOME/.m2/repositories/org/opendevstack/apiservice"
@echo "$(GREEN)✓ Maven cache clean complete$(NC)"

## Compile the project
compile: check-java check-maven
@echo "$(BLUE)Compiling project...$(NC)"
Expand Down Expand Up @@ -270,6 +288,145 @@ all: jar docker
## Quick start for development
quick-start: jar run-jar

# =============================================================================
# Database targets (Liquibase / PostgreSQL)
#
# Required environment variables (export them or source persistence/.env):
# ODS_API_SERVICE_DB_HOST PostgreSQL hostname or IP
# ODS_API_SERVICE_DB_PORT PostgreSQL port (default: 5432)
# ODS_API_SERVICE_DB_NAME Database name (ods_api_service)
# ODS_API_SERVICE_DB_USER Application user (ods_api_service)
# ODS_API_SERVICE_DB_PASSWORD Application user password
#
# Quick start:
# cp persistence/liquibase.properties.example persistence/.env
# # edit persistence/.env with real values
# source persistence/.env && make db-migrate
#
# Port-forward variables (can be overridden on the command line):
# NAMESPACE Kubernetes/OpenShift namespace where PostgreSQL runs (REQUIRED)
# DB_K8S_SERVICE Name of the PostgreSQL Service in the cluster (default: ods-api-service-postgresql)
# DB_PF_LOCAL_PORT Local port to bind on the developer machine (default: 5432)
# DB_PF_REMOTE_PORT PostgreSQL port exposed by the Service in-cluster (default: 5432)
#
# Example:
# make db-port-forward NAMESPACE=ods-dev
# make db-port-forward NAMESPACE=ods-prod DB_K8S_SERVICE=postgresql DB_PF_LOCAL_PORT=15432
# =============================================================================

# Build the JDBC URL from individual host/port/name parts.
# ODS_API_SERVICE_DB_PORT defaults to 5432 when not set.
DB_PORT ?= $(or $(ODS_API_SERVICE_DB_PORT),5432)
DB_JDBC_URL = jdbc:postgresql://$(ODS_API_SERVICE_DB_HOST):$(DB_PORT)/$(ODS_API_SERVICE_DB_NAME)

# Kubernetes port-forward defaults (all overridable on the command line)
DB_K8S_SERVICE ?= ods-api-service-postgresql
DB_PF_LOCAL_PORT ?= 5432
DB_PF_REMOTE_PORT ?= 5432

DB_MAVEN_ARGS = -pl persistence \
-Dliquibase.url=$(DB_JDBC_URL) \
-Dliquibase.username=$(ODS_API_SERVICE_DB_USER) \
-Dliquibase.password=$(ODS_API_SERVICE_DB_PASSWORD)

## [DB] Verify required DB environment variables are set
db-check-env:
@missing=; \
for var in ODS_API_SERVICE_DB_HOST ODS_API_SERVICE_DB_NAME ODS_API_SERVICE_DB_USER ODS_API_SERVICE_DB_PASSWORD; do \
if [ -z "$$(eval echo \$$$$var)" ]; then missing="$$missing $$var"; fi; \
done; \
if [ -n "$$missing" ]; then \
echo "$(RED)Error: the following required environment variables are not set:$(NC)"; \
for v in $$missing; do echo " $$v"; done; \
echo ""; \
echo "$(YELLOW)Hint: copy persistence/liquibase.properties.example to persistence/.env,"; \
echo " fill in the values, then run: source persistence/.env$(NC)"; \
exit 1; \
fi
@echo "$(GREEN)✓ DB environment variables OK$(NC)"

## [DB] Validate the Liquibase changelog syntax (no DB connection required)
db-validate: check-maven
@echo "$(BLUE)Validating Liquibase changelog...$(NC)"
$(MAVEN_WRAPPER) -pl persistence liquibase:validate
@echo "$(GREEN)✓ Changelog validation complete$(NC)"

## [DB] Show pending changesets that have not yet been applied
db-status: db-check-env check-maven
@echo "$(BLUE)Checking pending Liquibase changesets...$(NC)"
@echo "$(YELLOW)Target: $(DB_JDBC_URL)$(NC)"
$(MAVEN_WRAPPER) $(DB_MAVEN_ARGS) liquibase:status

## [DB] Apply all pending changesets to the database
db-migrate: db-check-env check-maven
@echo "$(BLUE)Running Liquibase migrations...$(NC)"
@echo "$(YELLOW)Target: $(DB_JDBC_URL)$(NC)"
$(MAVEN_WRAPPER) $(DB_MAVEN_ARGS) liquibase:update
@echo "$(GREEN)✓ Migrations applied$(NC)"

## [DB] Roll back the last applied changeset (usage: make db-rollback)
db-rollback: db-check-env check-maven
@echo "$(YELLOW)Rolling back last changeset on: $(DB_JDBC_URL)$(NC)"
$(MAVEN_WRAPPER) $(DB_MAVEN_ARGS) liquibase:rollback -Dliquibase.rollbackCount=1
@echo "$(GREEN)✓ Rollback complete$(NC)"

## [DB] Tag the current database state (usage: make db-tag TAG=v1.0.0)
db-tag: db-check-env check-maven
@if [ -z "$(TAG)" ]; then \
echo "$(RED)Error: TAG is required. Usage: make db-tag TAG=v1.0.0$(NC)"; \
exit 1; \
fi
@echo "$(BLUE)Tagging database state as '$(TAG)'...$(NC)"
$(MAVEN_WRAPPER) $(DB_MAVEN_ARGS) liquibase:tag -Dliquibase.tag=$(TAG)
@echo "$(GREEN)✓ Database tagged as '$(TAG)'$(NC)"

## [DB] Port-forward the cluster PostgreSQL Service to localhost (usage: make db-port-forward NAMESPACE=<ns>)
db-port-forward:
@if [ -z "$(NAMESPACE)" ]; then \
echo "$(RED)Error: NAMESPACE is required.$(NC)"; \
echo "$(YELLOW)Usage: make db-port-forward NAMESPACE=<namespace>$(NC)"; \
echo "$(YELLOW)Optionally override DB_K8S_SERVICE (default: $(DB_K8S_SERVICE)),$(NC)"; \
echo "$(YELLOW) DB_PF_LOCAL_PORT (default: $(DB_PF_LOCAL_PORT)) or DB_PF_REMOTE_PORT (default: $(DB_PF_REMOTE_PORT)).$(NC)"; \
exit 1; \
fi
@if ! command -v kubectl >/dev/null 2>&1; then \
echo "$(RED)Error: kubectl not found in PATH$(NC)"; \
exit 1; \
fi
@echo "$(BLUE)Port-forwarding PostgreSQL service...$(NC)"
@echo "$(YELLOW) Namespace : $(NAMESPACE)$(NC)"
@echo "$(YELLOW) Service : $(DB_K8S_SERVICE)$(NC)"
@echo "$(YELLOW) Mapping : localhost:$(DB_PF_LOCAL_PORT) → $(DB_K8S_SERVICE):$(DB_PF_REMOTE_PORT)$(NC)"
@echo ""
@echo "$(YELLOW)Once forwarding is active, connect with:$(NC)"
@echo " psql -h localhost -p $(DB_PF_LOCAL_PORT) -U \$$ODS_API_SERVICE_DB_USER \$$ODS_API_SERVICE_DB_NAME"
@echo " # or run migrations against the forwarded port:"
@echo " ODS_API_SERVICE_DB_HOST=localhost ODS_API_SERVICE_DB_PORT=$(DB_PF_LOCAL_PORT) make db-migrate"
@echo ""
@echo "$(YELLOW)Press Ctrl+C to stop the tunnel$(NC)"
kubectl port-forward \
--namespace $(NAMESPACE) \
--address 0.0.0.0 \
service/$(DB_K8S_SERVICE) \
$(DB_PF_LOCAL_PORT):$(DB_PF_REMOTE_PORT)

db-docker-build-db:
@echo "$(BLUE)Building Docker image for database...$(NC)"
@docker build --file ../ods-core/ods-api-service/docker/Dockerfile.database --tag $(PROJECT_NAME)-db:18 ../ods-core/ods-api-service/docker/
@echo "$(GREEN)✓ Database Docker image built: $(PROJECT_NAME)-db:$(DOCKER_TAG)$(NC)"

db-docker-run-db: db-docker-build-db
@echo "$(BLUE)Running PostgreSQL database in Docker...$(NC)"
@echo "$(YELLOW)Access the database at: localhost:5432$(NC)"
@echo "$(YELLOW)PostgreSQL credentials: user=ods_api_service, db=ods_api_service$(NC)"
@echo "$(YELLOW)Press Ctrl+C to stop$(NC)"
docker run -p 5432:5432 \
-e POSTGRES_USER=ods_api_service \
-e POSTGRES_PASSWORD=ods_api_service \
-e POSTGRES_DB=ods_api_service \
$(PROJECT_NAME)-db:18


## Clean everything including Docker images
clean-all: clean
@echo "$(BLUE)Cleaning Docker images...$(NC)"
Expand Down
48 changes: 45 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The system will expose RESTful APIs for third-party client applications and futu
- [Quick Start](#quick-start)
- [Build Options](#build-options)
- [Running the Application](#running-the-application)
- [Security & Authentication](#security--authentication)
- [Development Workflow](#development-workflow)
- [Docker Support](#docker-support)
- [Advanced Usage](#advanced-usage)
Expand All @@ -19,7 +20,7 @@ The system will expose RESTful APIs for third-party client applications and futu
Before using the Makefile, ensure you have the following installed:

### Required
- **Java 17+** - The project uses Java 21
- **Java 21+** - The project uses Java 21
- **Maven** - Maven wrapper (`mvnw`) is included in the project
- **Make** - For running Makefile commands

Expand Down Expand Up @@ -54,7 +55,7 @@ Build a traditional Spring Boot JAR file:
```bash
make jar
```
- Output: `core/target/core-0.0.2-SNAPSHOT.jar`
- Output: `core/target/core-0.0.3.jar`
- Includes all dependencies
- Standard Spring Boot startup time

Expand Down Expand Up @@ -112,7 +113,48 @@ Once running, the application is available at:
- **Swagger UI**: http://localhost:8080/swagger-ui.html
- **Actuator Health**: http://localhost:8080/actuator/health

## 💻 Development Workflow
## � Security & Authentication

The service is an OAuth2 Resource Server that validates incoming JWT access tokens.
Authentication is configured entirely through environment variables.

### Multiple JWT Issuers

The API accepts tokens from more than one trusted identity provider. Each issuer
pairs an issuer URI (the token `iss` claim) with the JWK set URI used to validate
the token signature. The incoming token's `iss` claim selects the matching issuer.

| Variable | Description |
|----------|-------------|
| `OAUTH2_ISSUER_V1` | Issuer URI (`iss` claim) of the first trusted identity provider |
| `OAUTH2_JWK_SET_URI_V1` | JWK set URI used to verify signatures for issuer V1 |
| `OAUTH2_ISSUER_V2` | Issuer URI (`iss` claim) of the second trusted identity provider |
| `OAUTH2_JWK_SET_URI_V2` | JWK set URI used to verify signatures for issuer V2 |

### Allowed Audiences

Audiences are shared across all configured issuers. A valid token must carry at
least one of these values in its `aud` claim.

| Variable | Description |
|----------|-------------|
| `OAUTH2_AUDIENCE` | First accepted audience value |
| `OAUTH2_AUDIENCE2` | Second accepted audience value |

### Marketplace OBO Bypass

Calls to the Marketplace service normally perform an On-Behalf-Of (OBO) token
exchange. When the incoming token already targets the configured bypass audience
and scope, the OBO exchange is skipped and the token is forwarded as-is.

| Variable | Description |
|----------|-------------|
| `MARKETPLACE_BYPASS_AUDIENCE` | Audience that must be present in the token `aud` claim to skip the OBO exchange |
| `MARKETPLACE_BYPASS_SCOPE` | Scope that must be present in the token `scp` claim to skip the OBO exchange |

> If either bypass value is unset, the OBO exchange is always performed.

## �💻 Development Workflow

### Quick Development Cycle
Clean, compile, and test in one command:
Expand Down
Loading
Loading