Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
2c764b5
chore: Update all non-major dependencies (v3.x.x) (#4416)
zowe-robot Dec 19, 2025
ccffa6d
chore: update docker-compose (#4410)
pablocarle Dec 19, 2025
e0c2235
chore: Node.js enabler polishing (#4436)
taban03 Dec 19, 2025
484f486
fix: remove reversecategorize filter from attls (#4441)
pablocarle Dec 29, 2025
5343f02
chore: Remove cloud gateway config from docker (#4443)
richard-salac Dec 30, 2025
fa1c3ec
fix: Loading jwks with nonstrict certificate validation (#4442)
richard-salac Dec 30, 2025
9daea3a
chore: Include testing of Node.JS and Python enabler sample apps and …
taban03 Jan 2, 2026
50f6ab7
Automatic update for the Changelog for release (#4352)
zowe-robot Jan 6, 2026
3aa4c35
chore: failing build process (#4448)
achmelo Jan 8, 2026
3331d0a
[Gradle Release plugin] [skip ci] Before tag commit 'v3.5.0'.
zowe-robot Jan 8, 2026
d0bcee0
[Gradle Release plugin] Create new version: 'v3.5.1-SNAPSHOT'.
zowe-robot Jan 8, 2026
113f4cf
[skip ci] Update version
zowe-robot Jan 8, 2026
2f9e149
[Gradle Release plugin] [skip ci] Before tag commit 'v3.5.1'.
zowe-robot Jan 9, 2026
51bf96e
[Gradle Release plugin] Create new version: 'v3.5.2-SNAPSHOT'.
zowe-robot Jan 9, 2026
c750887
[skip ci] Update version
zowe-robot Jan 9, 2026
6040e4e
chore: publish apiml image (#4439)
achmelo Jan 9, 2026
e125f99
fix: disable hostname validation (#4452)
achmelo Jan 14, 2026
2225042
chore: Fix flaky tests (#4449)
nxhafa Jan 14, 2026
c995a20
fix: Fix error message during the shutdown of the Discovery service …
pavel-jares-bcm Jan 14, 2026
716e1df
chore: schedule image release (#4453)
achmelo Jan 15, 2026
f3a1284
[Gradle Release plugin] [skip ci] Before tag commit 'v3.5.2'.
zowe-robot Jan 16, 2026
8dbefcf
[Gradle Release plugin] Create new version: 'v3.5.3-SNAPSHOT'.
zowe-robot Jan 16, 2026
6a003ca
[skip ci] Update version
zowe-robot Jan 16, 2026
fdcca51
chore: Change nodejs enabler sample app port (#4455)
richard-salac Jan 16, 2026
364f61a
chore: Update all non-major dependencies (v3.x.x) (#4437)
zowe-robot Jan 20, 2026
0f1482a
fix: expect 401 on info endpoint (#4460)
achmelo Jan 22, 2026
980fbad
[Gradle Release plugin] [skip ci] Before tag commit 'v3.5.3'.
zowe-robot Jan 23, 2026
d2df31a
[Gradle Release plugin] Create new version: 'v3.5.4-SNAPSHOT'.
zowe-robot Jan 23, 2026
89bd0de
[skip ci] Update version
zowe-robot Jan 23, 2026
e9e1ba4
chore: Fix modulith service registration IT (#4458)
richard-salac Jan 23, 2026
f13d33f
chore: Update all non-major dependencies (v3.x.x) (#4462)
zowe-robot Jan 23, 2026
1ba8f29
chore: Update all non-major dependencies (v3.x.x) (#4467)
zowe-robot Jan 26, 2026
95235b3
chore: update npm token secret (#4471)
taban03 Jan 27, 2026
58f08e5
feat: Support binding on multiple network interfaces (#4457)
pavel-jares-bcm Jan 27, 2026
7ff39b1
chore: Update all non-major dependencies (v3.x.x) (#4472)
zowe-robot Jan 27, 2026
36f0f58
chore: upgrade libraries and resolve security vulnerabilities (#4470)
nxhafa Jan 27, 2026
0e1a039
fix: Fix infinispan configuration, exception handling, and the whole …
pavel-jares-bcm Jan 27, 2026
e0f0adf
API for delegating credentials to generate a z/OS PassTicket based on…
Goutham024 Jan 28, 2026
8b1bfc7
API for delegating credentials to generate a z/OS PassTicket based on…
Goutham024 Jan 28, 2026
cd2cfb5
API for delegating credentials to generate a z/OS PassTicket based on…
Goutham024 Jan 28, 2026
60fe2e2
API for delegating credentials to generate a z/OS PassTicket
Goutham024 Jan 29, 2026
dcc1512
Enabled the SAF Authorization for delegate passticket endpoint
Goutham024 Jan 29, 2026
d1dd57b
chore: Skip non modulith tests from modulith ha (#4477)
pablocarle Jan 29, 2026
9ddefa2
[Gradle Release plugin] [skip ci] Before tag commit 'v3.5.4'.
zowe-robot Jan 30, 2026
811e2f0
[Gradle Release plugin] Create new version: 'v3.5.5-SNAPSHOT'.
zowe-robot Jan 30, 2026
8d7bdf5
[skip ci] Update version
zowe-robot Jan 30, 2026
3d6244c
docs: Automatic update for the Changelog for release (#4475)
zowe-robot Jan 30, 2026
b28f0bb
fix: Log ignored certificates during client authentication (#4415)
hrishikesh-nalawade Feb 2, 2026
ce28617
fix: add referer header (#4479)
achmelo Feb 2, 2026
97a3068
fix: Set JVM console encoding to IBM-1047 for Java 21 (#4482)
hrishikesh-nalawade Feb 3, 2026
1030490
Addressed the RR review comments
Goutham024 Feb 4, 2026
9f17b46
API for delegating credentials to generate a z/OS PassTicket based on…
Goutham024 Jan 28, 2026
f7e75ad
API for delegating credentials to generate a z/OS PassTicket based on…
Goutham024 Jan 28, 2026
16616a6
API for delegating credentials to generate a z/OS PassTicket based on…
Goutham024 Jan 28, 2026
a6f53fa
API for delegating credentials to generate a z/OS PassTicket
Goutham024 Jan 29, 2026
a12fb8c
Enabled the SAF Authorization for delegate passticket endpoint
Goutham024 Jan 29, 2026
1f4fa6b
[Gradle Release plugin] [skip ci] Before tag commit 'v3.5.4'.
zowe-robot Jan 30, 2026
b545036
[Gradle Release plugin] Create new version: 'v3.5.5-SNAPSHOT'.
zowe-robot Jan 30, 2026
2f5163a
Addressed the RR review comments
Goutham024 Feb 4, 2026
c73ea05
API for delegating credentials to generate a z/OS PassTicket based on…
Goutham024 Jan 28, 2026
72ae494
API for delegating credentials to generate a z/OS PassTicket based on…
Goutham024 Jan 28, 2026
c0fcdcd
API for delegating credentials to generate a z/OS PassTicket based on…
Goutham024 Jan 28, 2026
4d7c3f2
API for delegating credentials to generate a z/OS PassTicket
Goutham024 Jan 29, 2026
1005a4b
Enabled the SAF Authorization for delegate passticket endpoint
Goutham024 Jan 29, 2026
98ad289
Addressed the RR review comments
Goutham024 Feb 4, 2026
4873326
API for delegating credentials to generate a z/OS PassTicket based on…
Goutham024 Jan 28, 2026
a7f9faf
API for delegating credentials to generate a z/OS PassTicket based on…
Goutham024 Jan 28, 2026
1d1c0d0
API for delegating credentials to generate a z/OS PassTicket based on…
Goutham024 Jan 28, 2026
a74dcf9
API for delegating credentials to generate a z/OS PassTicket
Goutham024 Jan 29, 2026
16e93ae
Enabled the SAF Authorization for delegate passticket endpoint
Goutham024 Jan 29, 2026
4ef8221
Addressed the RR review comments
Goutham024 Feb 4, 2026
a0897e2
Merge branch 'zowe:v3.x.x' into passticket-delegation-api
Goutham024 Feb 4, 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
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ RouterFunction<ServerResponse> routes() {
.andRoute(path("/gateway/api/v1/auth/keys/public/current"), resendTo("/api/v1/auth/keys/public/current"))
.andRoute(path("/gateway/api/v1/auth/oidc-token/validate"), resendTo("/api/v1/auth/oidc-token/validate"))
.andRoute(path("/gateway/api/v1/auth/oidc/webfinger"), resendTo("/api/v1/auth/oidc/webfinger"))
.andRoute(path("/gateway/auth/check"), resendTo("/auth/check"));
.andRoute(path("/gateway/auth/check"), resendTo("/auth/check"))
.andRoute(path("/gateway/api/v1/auth/delegate/passticket"), resendTo("/api/v1/auth/delegate/passticket"));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/

package org.zowe.apiml.zaas.controllers;

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.zowe.apiml.passticket.PassTicketException;
import org.zowe.apiml.passticket.PassTicketService;
import org.zowe.apiml.zaas.security.mapping.NativeMapperWrapper;
import org.zowe.commons.usermap.MapperResponse;

import io.swagger.v3.oas.annotations.Hidden;
import lombok.Builder;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
* Controller offer method to control security. It can contain method for user
* and also method for calling services
* by gateway to distribute state of authentication between nodes.
*/
@RequiredArgsConstructor
@RestController
@RequestMapping(SecurityTokenServiceController.CONTROLLER_PATH)
@Slf4j
public class SecurityTokenServiceController {

@Value("${apiml.security.oidc.registry:}")
protected String registry;

private final PassTicketService passTicketService;
private final NativeMapperWrapper nativeMapper;

public static final String CONTROLLER_PATH = "/zaas/api/v1/auth/delegate";
public static final String PASSTICKET_PATH = "/passticket";

@PostMapping(value = SecurityTokenServiceController.PASSTICKET_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
@ConditionalOnProperty(value = "apiml.security.delegatePassticket.enabled", havingValue = "true", matchIfMissing = false)
@PreAuthorize("@safMethodSecurityExpressionRoot.hasSafServiceResourceAccess('DELEGATE.PASSTICKET', 'READ',#root)")
@Hidden
public ResponseEntity<PassTicketResponse> getPassTicket(@RequestBody PassTicketRequest passticketRequest)
throws Exception {
String applID = passticketRequest.getApplId();
String emailID = passticketRequest.getEmailId();
String zosUserId = "";

if (Strings.isBlank(emailID) || Strings.isBlank(applID)) {
log.debug("getPassTicket: Invalid applId or EmailId");
return ResponseEntity.badRequest().build();
}
try {
MapperResponse response = nativeMapper.getUserIDForDN(emailID, registry);
if (response.getRc() == 0 && StringUtils.isNotEmpty(response.getUserId())) {
zosUserId = response.getUserId();
}
log.debug("getPassTicket: Processing request for ZOS userId: {}", zosUserId);
var ticket = passTicketService.generate(zosUserId, applID);
log.debug("getPassTicket: Request processed with emailId: {} and ZOS userId: {}", emailID, zosUserId);
return ResponseEntity.ok(new PassTicketResponse(ticket, zosUserId));
} catch (PassTicketException ex) {
log.error("getPassTicket: Failed to generate passticket", ex);
return ResponseEntity.internalServerError().build();
}
}

@Data
public static class PassTicketRequest {
private String emailId;
private String applId;
}

@Data
@Builder
public static class PassTicketResponse {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a bit of inconsistency with the naming convention used in the /ticket endpoint, where we use applicationName rather than applId, and ticket rather than passticket. I probably prefer this one, but worthy to mention that

private String passticket;
private String tsoUserid;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,53 @@ SecurityFilterChain authZaasEndpointsFilterChain(HttpSecurity http) throws Excep
}



/**
* Secures endpoints:
* - /auth/delegate/passticket
*
* Requires authentication by a client certificate forwarded form Gateway or basic authentication, supports only credentials in header.
* Order of custom filters:
* - CategorizeCertsFilter - checks for forwarded client certificate and put it into a custom request attribute
* - X509AuthAwareFilter - attempts to log in using a user using forwarded client certificate, replaces pre-authentication in security context by the authentication result
*/
@Configuration
@RequiredArgsConstructor
@Order(10)
class DelegatePassticketProtectedEndpoints {

private final CompoundAuthProvider compoundAuthProvider;

@Bean
SecurityFilterChain passticketProtectedEndpointsFilterChain(HttpSecurity http) throws Exception {
baseConfigure(http.securityMatchers(matchers -> matchers.requestMatchers( // no http method to catch all attempts to login and handle them here. Otherwise it falls to default filterchain and tries to route the calls, which doesnt make sense
"/zaas/api/v1/auth/delegate/passticket"
)))
.authorizeHttpRequests(requests -> requests
.anyRequest().authenticated())
.authenticationProvider(compoundAuthProvider) // for authenticating credentials
.with(new CustomSecurityFilters(), Customizer.withDefaults());
return http.build();
}

private class CustomSecurityFilters extends AbstractHttpConfigurer<AccessToken.CustomSecurityFilters, HttpSecurity> {
@Override
public void configure(HttpSecurity http) {
http.addFilterAfter(new CategorizeCertsFilter(publicKeyCertificatesBase64, certificateValidator), org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter.class)
.addFilterAfter(x509ForwardingAwareAuthenticationFilter(), CategorizeCertsFilter.class);
}

private X509ForwardingAwareAuthenticationFilter x509ForwardingAwareAuthenticationFilter() {
return new X509AuthAwareFilter("/**",
handlerInitializer.getAuthenticationFailureHandler(),
x509AuthenticationProvider);
}

}

}


/**
* Query and Ticket and Refresh endpoints share single filter that handles auth with and without certificate. This logic is encapsulated in the queryFilter or ticketFilter.
* Query endpoint does not require certificate to be present in RequestContext. It verifies JWT token.
Expand Down
2 changes: 2 additions & 0 deletions zaas-service/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ apiml:
urls:
authenticate: https://localhost:10013/zss/saf/authenticate
verify: https://localhost:10013/zss/saf/verify
delegatePassticket:
enabled: true
health:
protected: true
spring:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/

package org.zowe.apiml.zaas.controllers;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import static org.mockito.ArgumentMatchers.anyString;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import org.mockito.MockitoAnnotations;
import org.springframework.http.ResponseEntity;
import org.zowe.apiml.passticket.PassTicketService;
import org.zowe.apiml.zaas.security.mapping.NativeMapperWrapper;
import org.zowe.commons.usermap.MapperResponse;

class SecurityTokenServiceControllerTest {

@Mock
private PassTicketService passTicketService;

@Mock
private NativeMapperWrapper nativeMapper;

@InjectMocks
private SecurityTokenServiceController SecurityTokenServiceController;

@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
SecurityTokenServiceController.registry = "testRegistry";
}

@Nested
class SuccessfulRequests {

@Test
void shouldReturnPassTicketWhenRequestIsValid() throws Exception {
SecurityTokenServiceController.PassTicketRequest request = new SecurityTokenServiceController.PassTicketRequest();
request.setApplId("TESTAPP");
request.setEmailId("test@company.com");

MapperResponse mapperResponse = new MapperResponse("ZOSUSER", 0, 0, 0, 0);

when(nativeMapper.getUserIDForDN("test@company.com", "testRegistry"))
.thenReturn(mapperResponse);
when(passTicketService.generate("ZOSUSER", "TESTAPP"))
.thenReturn("TICKET123");

ResponseEntity<SecurityTokenServiceController.PassTicketResponse> response =
SecurityTokenServiceController.getPassTicket(request);

assertEquals(200, response.getStatusCode().value());
assertNotNull(response.getBody());
assertEquals("TICKET123", response.getBody().getPassticket());
assertEquals("ZOSUSER", response.getBody().getTsoUserid());

verify(nativeMapper)
.getUserIDForDN("test@company.com", "testRegistry");
verify(passTicketService)
.generate("ZOSUSER", "TESTAPP");
}

@Test
void shouldReturnPassTicketWhenMapperReturnsEmptyUser() throws Exception {
SecurityTokenServiceController.PassTicketRequest request = new SecurityTokenServiceController.PassTicketRequest();
request.setApplId("APPID");
request.setEmailId("test@company.com");

MapperResponse mapperResponse = new MapperResponse("", 0, 0, 0, 0);

when(nativeMapper.getUserIDForDN(anyString(), anyString()))
.thenReturn(mapperResponse);
when(passTicketService.generate("", "APPID"))
.thenReturn("TICKET123");

ResponseEntity<SecurityTokenServiceController.PassTicketResponse> response =
SecurityTokenServiceController.getPassTicket(request);

assertEquals(200, response.getStatusCode().value());
assertEquals("TICKET123", response.getBody().getPassticket());
assertEquals("", response.getBody().getTsoUserid());
}
}

@Nested
class BadRequests {

@Test
void shouldReturnBadRequestWhenEmailIsBlank() throws Exception {
SecurityTokenServiceController.PassTicketRequest request = new SecurityTokenServiceController.PassTicketRequest();
request.setApplId("APPID");
request.setEmailId("");

ResponseEntity<SecurityTokenServiceController.PassTicketResponse> response =
SecurityTokenServiceController.getPassTicket(request);

assertEquals(400, response.getStatusCode().value());
verifyNoInteractions(passTicketService, nativeMapper);
}

@Test
void shouldReturnBadRequestWhenApplIdIsBlank() throws Exception {
SecurityTokenServiceController.PassTicketRequest request = new SecurityTokenServiceController.PassTicketRequest();
request.setEmailId("test@company.com");
request.setApplId("");

ResponseEntity<SecurityTokenServiceController.PassTicketResponse> response =
SecurityTokenServiceController.getPassTicket(request);

assertEquals(400, response.getStatusCode().value());
verifyNoInteractions(passTicketService, nativeMapper);
}
}

@Nested
class FailureScenarios {

@Test
void shouldPropagateExceptionWhenNativeMapperFails() throws Exception {
SecurityTokenServiceController.PassTicketRequest request = new SecurityTokenServiceController.PassTicketRequest();
request.setApplId("APPID");
request.setEmailId("test@company.com");

when(nativeMapper.getUserIDForDN(anyString(), anyString()))
.thenThrow(new RuntimeException("Mapper failed"));

RuntimeException exception = assertThrows(
RuntimeException.class,
() -> SecurityTokenServiceController.getPassTicket(request)
);

assertEquals("Mapper failed", exception.getMessage());
}
}
}
2 changes: 2 additions & 0 deletions zaas-service/src/test/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ apiml:
urls:
authenticate: https://localhost:10013/zss/saf/authenticate
verify: https://localhost:10013/zss/saf/verify
delegatePassticket:
Copy link
Contributor

@taban03 taban03 Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we should document this new property and endpoint in zowe docs

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've created issue zowe/docs-site#4920 to do the doc updates.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks Joe

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR zowe/docs-site#4925 covering the endpoint.
I wasn't sure which docs section to link it from so I added it to the page https://docs.zowe.org/stable/user-guide/api-mediation/configuration-authorization.
Happy to have it moved elsewhere, or else provide more information on the API and its usage

enabled: true
health:
protected: false
spring:
Expand Down
Loading