Skip to content
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
41 changes: 41 additions & 0 deletions core-contracts/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.opendevstack.apiservice</groupId>
<artifactId>devstack-api-service</artifactId>
<version>0.0.3</version>
</parent>

<artifactId>core-contracts</artifactId>
<name>core-contracts</name>

<dependencies>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.opendevstack.apiservice.core.contracts.api;

import java.util.List;

public interface ApiModule {

String getName();

String getBasePath();

List<String> getSupportedVersions();

boolean isLocal();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.opendevstack.apiservice.core.contracts.auth;

public enum AuthType {
NONE,
OBO,
CLIENT_CREDENTIALS
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.opendevstack.apiservice.core.contracts.auth;

public enum AuthorizationDecision {

PERMIT,
DENY,
ABSTAIN;

public static AuthorizationDecision combine(AuthorizationDecision a, AuthorizationDecision b) {
if (a == DENY || b == DENY) {
return DENY;
}
if (a == PERMIT || b == PERMIT) {
return PERMIT;
}
return ABSTAIN;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.opendevstack.apiservice.core.contracts.persistence;

import org.opendevstack.apiservice.core.contracts.registry.ApiDefinition;

import java.util.List;
import java.util.Optional;

/**
* Data access contract for API definitions.
* Implementations are provided by the persistence module.
*/
public interface ApiDefinitionDao {

Optional<ApiDefinition> findByApiId(String apiId);

Optional<ApiDefinition> findByBasePathAndVersion(String basePath, String version);

List<ApiDefinition> findAllEnabled();

List<ApiDefinition> findAll();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.opendevstack.apiservice.core.contracts.persistence;

import java.util.Optional;

/**
* Data access contract for registered clients.
* Implementations are provided by the persistence module.
*/
public interface ClientDao {

Optional<ClientInfo> findByClientId(String clientId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.opendevstack.apiservice.core.contracts.persistence;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;

import java.util.UUID;

@Getter
@Accessors(fluent = true)
@RequiredArgsConstructor
@EqualsAndHashCode
@ToString
public final class ClientInfo {

private final UUID id;
private final String clientId;
private final String name;
private final boolean enabled;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.opendevstack.apiservice.core.contracts.persistence;

import org.opendevstack.apiservice.core.contracts.policy.PolicyRule;

import java.util.List;

/**
* Data access contract for authorization policies.
* Implementations are provided by the persistence module.
*/
public interface PolicyDao {

List<PolicyRule> findByApiDefinitionId(String apiDefinitionId);

List<PolicyRule> findByApiDefinitionIdAndClientId(String apiDefinitionId, String clientId);

List<PolicyRule> findGlobalByApiDefinitionId(String apiDefinitionId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.opendevstack.apiservice.core.contracts.policy;

import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.opendevstack.apiservice.core.contracts.registry.ApiDefinition;

import java.util.Map;

@Getter
@AllArgsConstructor
public class PolicyContext {

private final String clientId;
private final String subject;
private final Map<String, Object> claims;
private final ApiDefinition apiDefinition;
private final HttpServletRequest request;
private final PolicyRule activeRule;
private final Map<String, Object> requestBody;

public PolicyContext(String clientId, String subject, Map<String, Object> claims,
ApiDefinition apiDefinition, HttpServletRequest request) {
this(clientId, subject, claims, apiDefinition, request, null, null);
}

/**
* Returns a new context with the given rule set, leaving this instance unchanged.
*/
public PolicyContext withRule(PolicyRule rule) {
return new PolicyContext(clientId, subject, claims, apiDefinition, request, rule, requestBody);
}

/**
* Returns a new context with the given request body, leaving this instance unchanged.
*/
public PolicyContext withRequestBody(Map<String, Object> body) {
return new PolicyContext(clientId, subject, claims, apiDefinition, request, activeRule, body);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.opendevstack.apiservice.core.contracts.policy;

import org.opendevstack.apiservice.core.contracts.auth.AuthorizationDecision;

public interface PolicyEvaluator {

boolean supports(String policyType);

AuthorizationDecision evaluate(PolicyContext context);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.opendevstack.apiservice.core.contracts.policy;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Map;
import java.util.UUID;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class PolicyRule {

private UUID id;
private String apiDefinitionId;
private String clientId;
private String policyType;
private Map<String, Object> config;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.opendevstack.apiservice.core.contracts.policy;

/**
* Well-known policy type constants used by core.
* Modules are free to define their own policy type strings
* without modifying this class.
*/
public final class PolicyTypes {

private PolicyTypes() {}

public static final String ALLOWED_CLIENTS = "ALLOWED_CLIENTS";
public static final String SCOPE_REQUIRED = "SCOPE_REQUIRED";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.opendevstack.apiservice.core.contracts.registry;

import java.util.Set;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.opendevstack.apiservice.core.contracts.auth.AuthType;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiDefinition {

private String id;
private String name;
private String basePath;
private String version;
private Set<AuthType> authTypes;
private boolean isPublic;
private String proxyUrl;
private boolean enabled;

public boolean isLocal() {
return proxyUrl == null || proxyUrl.isBlank();
}

public boolean requiresAuth() {
return authTypes != null
&& !authTypes.isEmpty()
&& !(authTypes.size() == 1 && authTypes.contains(AuthType.NONE));
}
}
6 changes: 6 additions & 0 deletions persistence/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.opendevstack.apiservice</groupId>
<artifactId>core-contracts</artifactId>
<version>${project.version}</version>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package org.opendevstack.apiservice.persistence.dao;

import lombok.extern.slf4j.Slf4j;
import org.opendevstack.apiservice.core.contracts.auth.AuthType;
import org.opendevstack.apiservice.core.contracts.persistence.ApiDefinitionDao;
import org.opendevstack.apiservice.core.contracts.registry.ApiDefinition;
import org.opendevstack.apiservice.persistence.entity.ApiDefinitionEntity;
import org.opendevstack.apiservice.persistence.repository.ApiDefinitionJpaRepository;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ApiDefinitionDaoImpl implements ApiDefinitionDao {

private final ApiDefinitionJpaRepository repository;

public ApiDefinitionDaoImpl(ApiDefinitionJpaRepository repository) {
this.repository = repository;
}

@Override
public Optional<ApiDefinition> findByApiId(String apiId) {
return repository.findByApiId(apiId)
.map(this::toDto);
}

@Override
public Optional<ApiDefinition> findByBasePathAndVersion(String basePath, String version) {
return repository.findByBasePathAndVersion(basePath, version)
.map(this::toDto);
}

@Override
public List<ApiDefinition> findAllEnabled() {
return repository.findByEnabledTrue().stream()
.map(this::toDto)
.toList();
}

@Override
public List<ApiDefinition> findAll() {
return repository.findAll().stream()
.map(this::toDto)
.toList();
}

private ApiDefinition toDto(ApiDefinitionEntity entity) {
Set<AuthType> authTypes = Arrays.stream(entity.getAuthTypes())
.map(s -> {
try {
return AuthType.valueOf(s);
} catch (IllegalArgumentException e) {
log.warn("Unknown AuthType '{}' in api_definitions row {}", s, entity.getApiId());
return null;
}
})
.filter(java.util.Objects::nonNull)
.collect(Collectors.toSet());

return new ApiDefinition(
entity.getApiId(),
entity.getName(),
entity.getBasePath(),
entity.getVersion(),
authTypes,
entity.isPublic(),
entity.getProxyUrl(),
entity.isEnabled()
);
}
}
Loading
Loading