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
1 change: 1 addition & 0 deletions itests/src/test/java/org/apache/unomi/itests/AllITs.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
GraphQLProfileAliasesIT.class,
SendEventActionIT.class,
ScopeIT.class,
V2CompatibilityModeIT.class,
HealthCheckIT.class,
LegacyQueryBuilderMappingIT.class,
})
Expand Down

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions itests/src/test/resources/events/viewEvent.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"sessionId": "test-session-id",
"events": [
{
"eventType": "view",
"scope": "testScope",
"source": {
"itemType": "site",
"scope": "testScope",
"itemId": "test-site"
},
"target": {
"itemType": "page",
"scope": "testScope",
"itemId": "test-page",
"properties": {
"pageInfo": {
"pageID": "test-page",
"nodeType": "jnt:page",
"pageName": "Test Page",
"pagePath": "/test-page",
"templateName": "test",
"destinationURL": "http://localhost:8080/test-page",
"destinationSearch": "",
"referringURL": "http://localhost:8080/",
"language": "en",
"categories": [],
"tags": [],
"sameDomainReferrer": false
},
"consentTypes": []
}
},
"flattenedProperties": {}
}
]
}
1 change: 1 addition & 0 deletions kar/src/main/feature/feature.xml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
<feature>unomi-services</feature>
<feature>unomi-cxs-privacy-extension-services</feature>
<configfile finalname="/etc/org.apache.unomi.schema.cfg">mvn:org.apache.unomi/unomi-json-schema-services/${project.version}/cfg/schemacfg</configfile>
<configfile finalname="/etc/org.apache.unomi.rest.authentication.cfg">mvn:org.apache.unomi/unomi-rest/${project.version}/cfg/restauth</configfile>
<bundle start="false">mvn:org.apache.unomi/unomi-json-schema-services/${project.version}</bundle>
<bundle start="false">mvn:org.apache.unomi/unomi-rest/${project.version}</bundle>
<bundle start="false">mvn:org.apache.unomi/unomi-json-schema-rest/${project.version}</bundle>
Expand Down
6 changes: 6 additions & 0 deletions package/src/main/resources/etc/custom.system.properties
Original file line number Diff line number Diff line change
Expand Up @@ -511,3 +511,9 @@ karaf.local.roles = admin,manager,viewer,systembundles,ROLE_UNOMI_ADMIN,ROLE_UNO
#######################################################################################################################
org.apache.unomi.goals.refresh.interval=${env:UNOMI_GOALS_REFRESH_INTERVAL:-5000}
org.apache.unomi.campaigns.refresh.interval=${env:UNOMI_CAMPAIGNS_REFRESH_INTERVAL:-5000}

#######################################################################################################################
## REST API Authorization Settings ##
#######################################################################################################################
org.apache.unomi.rest.authentication.v2CompatibilityModeEnabled=${env:UNOMI_REST_AUTHENTICATION_V2COMPATIBILITYMODEENABLED:-false}
org.apache.unomi.rest.authentication.v2CompatibilityDefaultTenantId=${env:UNOMI_REST_AUTHENTICATION_V2COMPATIBILITYDEFAULTTENANTID:-default}
24 changes: 24 additions & 0 deletions rest/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,30 @@

<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>attach-artifacts</id>
<phase>package</phase>
<goals>
<goal>attach-artifact</goal>
</goals>
<configuration>
<artifacts>
<artifact>
<file>
src/main/resources/org.apache.unomi.rest.authentication.cfg
</file>
<type>cfg</type>
<classifier>restauth</classifier>
</artifact>
</artifacts>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ public void filter(ContainerRequestContext requestContext) throws IOException {
try {
String path = requestContext.getUriInfo().getPath();

// Check if V2 compatibility mode is enabled
if (restAuthenticationConfig.isV2CompatibilityModeEnabled()) {
handleV2CompatibilityMode(requestContext, path);
return;
}

// Tenant endpoints require JAAS authentication only
if (path.startsWith("tenants")) {
String authHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
Expand Down Expand Up @@ -239,6 +245,67 @@ public void filter(ContainerRequestContext requestContext) throws IOException {
}
}

/**
* Handle authentication in V2 compatibility mode.
* In this mode:
* - Public endpoints (like /context.json) require no authentication (like V2)
* - Protected events require IP + X-Unomi-Peer (like V2)
* - Private endpoints require system administrator authentication (like V2)
* - A default tenant is automatically used for all operations
*/
private void handleV2CompatibilityMode(ContainerRequestContext requestContext, String path) throws IOException {
// For public paths, allow access without authentication (like V2)
if (isPublicPath(requestContext)) {
String defaultTenantId = restAuthenticationConfig.getV2CompatibilityDefaultTenantId();
if (defaultTenantId != null) {
// Create a guest subject for public endpoints
Subject subject = securityService.createSubject(defaultTenantId, false);

// Set CXF security context
JAXRSUtils.getCurrentMessage().put(SecurityContext.class,
new RolePrefixSecurityContextImpl(subject, ROLE_CLASSIFIER, ROLE_CLASSIFIER_TYPE));

// Set the security service subject
securityService.setCurrentSubject(subject);

// Set the execution context for the default tenant
executionContextManager.setCurrentContext(executionContextManager.createContext(defaultTenantId));
return;
}
}

// For private endpoints, require system administrator authentication (like V2)
String authHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
if (authHeader != null && authHeader.startsWith(BASIC_AUTH_PREFIX)) {
try {
jaasAuthenticationFilter.filter(requestContext);
// Get the subject from the security context after successful JAAS auth
SecurityContext securityContext = JAXRSUtils.getCurrentMessage().get(SecurityContext.class);
if (securityContext != null) {
Subject subject = ((RolePrefixSecurityContextImpl) securityContext).getSubject();
// Set the authenticated subject in Unomi's security service
securityService.setCurrentSubject(subject);

// In V2 compatibility mode, use the default tenant for all operations
String defaultTenantId = restAuthenticationConfig.getV2CompatibilityDefaultTenantId();
if (defaultTenantId != null) {
executionContextManager.setCurrentContext(executionContextManager.createContext(defaultTenantId));
} else {
executionContextManager.setCurrentContext(ExecutionContext.systemContext());
}
}
return;
} catch (Exception e) {
logger.debug("V2 compatibility mode: JAAS authentication failed");
}
} else {
logger.debug("V2 compatibility mode: Missing Basic Auth header for private endpoint");
}

// If we get here, no valid authentication was provided
unauthorized(requestContext);
}

private String[] extractBasicAuthCredentials(String authHeader) {
try {
String base64Credentials = authHeader.substring(BASIC_AUTH_PREFIX.length()).trim();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,23 @@ public interface RestAuthenticationConfig {
* @return Global roles separated with single white spaces, like: "ROLE1 ROLE2 ROLE3"
*/
String getGlobalRoles();

/**
* Check if V2 compatibility mode is enabled.
* When enabled, V2 clients can use Unomi V3 without requiring API keys:
* - Public endpoints (like /context.json) require no authentication (like V2)
* - Private endpoints require system administrator authentication (like V2)
* - A default tenant is automatically used for all operations
*
* @return true if V2 compatibility mode is enabled, false otherwise
*/
boolean isV2CompatibilityModeEnabled();

/**
* Get the default tenant ID to use in V2 compatibility mode.
* This tenant will be used for all operations when V2 compatibility mode is enabled.
*
* @return the default tenant ID
*/
String getV2CompatibilityDefaultTenantId();
}
Loading
Loading