From 6b91344c97e048cb971df5708506c65fdb2c422b Mon Sep 17 00:00:00 2001 From: Lyle Schemmerling Date: Tue, 24 Jun 2025 17:12:37 -0600 Subject: [PATCH 1/4] update clients --- .../io/fusionauth/domain/Application.java | 44 ++++++++++++++- .../domain/UniversalApplicationTenant.java | 55 +++++++++++++++++++ .../io/fusionauth/domain/form/FormField.java | 39 ++++++++++++- .../fusionauth/domain/jwt/RefreshToken.java | 6 +- .../fusionauth/domain/oauth2/OAuthError.java | 4 +- .../domain/reactor/ReactorStatus.java | 12 +++- .../search/ApplicationSearchCriteria.java | 9 ++- 7 files changed, 160 insertions(+), 9 deletions(-) create mode 100644 src/main/java/io/fusionauth/domain/UniversalApplicationTenant.java diff --git a/src/main/java/io/fusionauth/domain/Application.java b/src/main/java/io/fusionauth/domain/Application.java index be19f1d7..1dac51ce 100644 --- a/src/main/java/io/fusionauth/domain/Application.java +++ b/src/main/java/io/fusionauth/domain/Application.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2019-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -104,6 +104,8 @@ public class Application implements Buildable, Tenantable { public UUID themeId; + public UniversalConfiguration universalConfiguration = new UniversalConfiguration(); + public RegistrationUnverifiedOptions unverified = new RegistrationUnverifiedOptions(); public UUID verificationEmailTemplateId; @@ -147,6 +149,7 @@ public Application(Application other) { this.state = other.state; this.tenantId = other.tenantId; this.themeId = other.themeId; + this.universalConfiguration = new UniversalConfiguration(other.universalConfiguration); this.unverified = new RegistrationUnverifiedOptions(other.unverified); this.verificationEmailTemplateId = other.verificationEmailTemplateId; this.verificationStrategy = other.verificationStrategy; @@ -213,6 +216,7 @@ public boolean equals(Object o) { state == that.state && Objects.equals(tenantId, that.tenantId) && Objects.equals(themeId, that.themeId) && + Objects.equals(universalConfiguration, that.universalConfiguration) && Objects.equals(unverified, that.unverified) && Objects.equals(verificationEmailTemplateId, that.verificationEmailTemplateId) && Objects.equals(webAuthnConfiguration, that.webAuthnConfiguration) && @@ -256,7 +260,7 @@ public boolean hasDefaultRole() { @Override public int hashCode() { // active is omitted - return Objects.hash(accessControlConfiguration, authenticationTokenConfiguration, cleanSpeakConfiguration, data, emailConfiguration, externalIdentifierConfiguration, formConfiguration, id, insertInstant, jwtConfiguration, lambdaConfiguration, lastUpdateInstant, loginConfiguration, multiFactorConfiguration, name, oauthConfiguration, passwordlessConfiguration, registrationConfiguration, registrationDeletePolicy, roles, samlv2Configuration, scopes, state, tenantId, themeId, unverified, verificationEmailTemplateId, verificationStrategy, verifyRegistration, webAuthnConfiguration); + return Objects.hash(accessControlConfiguration, authenticationTokenConfiguration, cleanSpeakConfiguration, data, emailConfiguration, externalIdentifierConfiguration, formConfiguration, id, insertInstant, jwtConfiguration, lambdaConfiguration, lastUpdateInstant, loginConfiguration, multiFactorConfiguration, name, oauthConfiguration, passwordlessConfiguration, registrationConfiguration, registrationDeletePolicy, roles, samlv2Configuration, scopes, state, tenantId, themeId, universalConfiguration, unverified, verificationEmailTemplateId, verificationStrategy, verifyRegistration, webAuthnConfiguration); } public void normalize() { @@ -885,4 +889,40 @@ public String toString() { } } } + + public static class UniversalConfiguration { + + // List of tenants allowed to use the universal application + public List applicationTenants = new ArrayList<>(); + + // This is a flag to indicate that all tenants can use this universal application + public boolean global; + + // This is a flag to indicate that this application is universal and can be used by the configured application tenants + public boolean universal; + + @JacksonConstructor + public UniversalConfiguration() { + } + + public UniversalConfiguration(UniversalConfiguration other) { + this.applicationTenants = other.applicationTenants.stream().map(UniversalApplicationTenant::new).collect(Collectors.toList()); + this.global = other.global; + this.universal = other.universal; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + UniversalConfiguration that = (UniversalConfiguration) o; + return universal == that.universal && Objects.equals(applicationTenants, that.applicationTenants); + } + + @Override + public int hashCode() { + return Objects.hash(applicationTenants, universal); + } + } } diff --git a/src/main/java/io/fusionauth/domain/UniversalApplicationTenant.java b/src/main/java/io/fusionauth/domain/UniversalApplicationTenant.java new file mode 100644 index 00000000..1e9e9002 --- /dev/null +++ b/src/main/java/io/fusionauth/domain/UniversalApplicationTenant.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, FusionAuth, All Rights Reserved + * + * 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. + */ +package io.fusionauth.domain; + +import java.util.Objects; +import java.util.UUID; + +import com.inversoft.json.JacksonConstructor; + +/** + * @author Lyle Schemmerling + */ +public class UniversalApplicationTenant implements Buildable { + public UUID tenantId; + + @JacksonConstructor + public UniversalApplicationTenant() { + } + + public UniversalApplicationTenant(UUID tenantId) { + this.tenantId = tenantId; + } + + // copy constructor + public UniversalApplicationTenant(UniversalApplicationTenant other) { + this.tenantId = other.tenantId; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + UniversalApplicationTenant that = (UniversalApplicationTenant) o; + return Objects.equals(tenantId, that.tenantId); + } + + @Override + public int hashCode() { + return Objects.hashCode(tenantId); + } +} diff --git a/src/main/java/io/fusionauth/domain/form/FormField.java b/src/main/java/io/fusionauth/domain/form/FormField.java index 97221f8e..59a24d0c 100644 --- a/src/main/java/io/fusionauth/domain/form/FormField.java +++ b/src/main/java/io/fusionauth/domain/form/FormField.java @@ -1,5 +1,17 @@ /* - * Copyright (c) 2020-2022, FusionAuth, All Rights Reserved + * Copyright (c) 2020-2025, FusionAuth, All Rights Reserved + * + * 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. */ package io.fusionauth.domain.form; @@ -48,6 +60,31 @@ public class FormField implements Buildable { public FormFieldValidator validator = new FormFieldValidator(); + public FormField() { + // Default constructor + } + + public FormField(FormField formField) { + this.confirm = formField.confirm; + this.consentId = formField.consentId; + this.control = formField.control; + this.data = new LinkedHashMap<>(formField.data); + this.description = formField.description; + this.id = formField.id; + this.insertInstant = formField.insertInstant; + this.key = formField.key; + this.lastUpdateInstant = formField.lastUpdateInstant; + this.name = formField.name; + this.options = new ArrayList<>(formField.options); + this.required = formField.required; + this.type = formField.type; + if (formField.validator != null) { + this.validator = new FormFieldValidator(); + this.validator.expression = formField.validator.expression; + this.validator.enabled = formField.validator.enabled; + } + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/src/main/java/io/fusionauth/domain/jwt/RefreshToken.java b/src/main/java/io/fusionauth/domain/jwt/RefreshToken.java index 7678d01c..2c2b866d 100644 --- a/src/main/java/io/fusionauth/domain/jwt/RefreshToken.java +++ b/src/main/java/io/fusionauth/domain/jwt/RefreshToken.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -121,7 +121,9 @@ public boolean isExpired(Tenant tenant, Application application) { return startInstant.plusSeconds(tenant.httpSessionMaxInactiveInterval).isBefore(now); } else { // Refresh Token - JWTConfiguration jwtConfiguration = tenant.lookupJWTConfiguration(application); + JWTConfiguration jwtConfiguration = application != null && application.jwtConfiguration != null && application.jwtConfiguration.enabled + ? application.jwtConfiguration + : tenant.jwtConfiguration; // if the rt expired, we're done here. if (startInstant.plusMinutes(jwtConfiguration.refreshTokenTimeToLiveInMinutes).isBefore(now)) { diff --git a/src/main/java/io/fusionauth/domain/oauth2/OAuthError.java b/src/main/java/io/fusionauth/domain/oauth2/OAuthError.java index 974edb8a..3e455790 100644 --- a/src/main/java/io/fusionauth/domain/oauth2/OAuthError.java +++ b/src/main/java/io/fusionauth/domain/oauth2/OAuthError.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -99,6 +99,7 @@ public enum OAuthErrorReason { invalid_target_entity_scope, invalid_entity_permission_scope, invalid_user_id, + invalid_tenant_id, // Grant disabled grant_type_disabled, @@ -118,6 +119,7 @@ public enum OAuthErrorReason { missing_user_code, missing_user_id, missing_verification_uri, + missing_tenant_id, login_prevented, not_licensed, diff --git a/src/main/java/io/fusionauth/domain/reactor/ReactorStatus.java b/src/main/java/io/fusionauth/domain/reactor/ReactorStatus.java index 2b666e92..64e1deb5 100644 --- a/src/main/java/io/fusionauth/domain/reactor/ReactorStatus.java +++ b/src/main/java/io/fusionauth/domain/reactor/ReactorStatus.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2021-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,10 +57,14 @@ public class ReactorStatus { public boolean licensed; + public ReactorFeatureStatus organizationAdminApplication = ReactorFeatureStatus.UNKNOWN; + public ReactorFeatureStatus scimServer = ReactorFeatureStatus.UNKNOWN; public ReactorFeatureStatus threatDetection = ReactorFeatureStatus.UNKNOWN; + public ReactorFeatureStatus universalApplication = ReactorFeatureStatus.UNKNOWN; + public ReactorFeatureStatus webAuthn = ReactorFeatureStatus.UNKNOWN; public ReactorFeatureStatus webAuthnPlatformAuthenticators = ReactorFeatureStatus.UNKNOWN; @@ -87,8 +91,10 @@ public ReactorStatus(ReactorStatus other) { expiration = other.expiration; licenseAttributes.putAll(other.licenseAttributes); licensed = other.licensed; + organizationAdminApplication = other.organizationAdminApplication; scimServer = other.scimServer; threatDetection = other.threatDetection; + universalApplication = other.universalApplication; webAuthn = other.webAuthn; webAuthnPlatformAuthenticators = other.webAuthnPlatformAuthenticators; webAuthnRoamingAuthenticators = other.webAuthnRoamingAuthenticators; @@ -118,8 +124,10 @@ public boolean equals(Object o) { Objects.equals(expiration, that.expiration) && licensed == that.licensed && Objects.equals(licenseAttributes, that.licenseAttributes) && + organizationAdminApplication == that.organizationAdminApplication && scimServer == that.scimServer && threatDetection == that.threatDetection && + universalApplication == that.universalApplication && webAuthn == that.webAuthn && webAuthnPlatformAuthenticators == that.webAuthnPlatformAuthenticators && webAuthnRoamingAuthenticators == that.webAuthnRoamingAuthenticators; @@ -142,8 +150,10 @@ public int hashCode() { expiration, licensed, licenseAttributes, + organizationAdminApplication, scimServer, threatDetection, + universalApplication, webAuthn, webAuthnPlatformAuthenticators, webAuthnRoamingAuthenticators); diff --git a/src/main/java/io/fusionauth/domain/search/ApplicationSearchCriteria.java b/src/main/java/io/fusionauth/domain/search/ApplicationSearchCriteria.java index 98dcf232..d19e6f0c 100644 --- a/src/main/java/io/fusionauth/domain/search/ApplicationSearchCriteria.java +++ b/src/main/java/io/fusionauth/domain/search/ApplicationSearchCriteria.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023-2023, FusionAuth, All Rights Reserved + * Copyright (c) 2023-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package io.fusionauth.domain.search; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -31,6 +32,8 @@ * @author Spencer Witt */ public class ApplicationSearchCriteria extends BaseSearchCriteria implements Buildable { + public static final Set NullableFields = new HashSet<>(); + public static final Map SortableFields = new LinkedHashMap<>(); public String name; @@ -45,7 +48,7 @@ public ApplicationSearchCriteria prepare() { orderBy = defaultOrderBy(); } - orderBy = normalizeOrderBy(orderBy, SortableFields); + orderBy = normalizeOrderBy(orderBy, SortableFields, NullableFields); name = toSearchString(name); return this; } @@ -61,6 +64,8 @@ protected String defaultOrderBy() { } static { + NullableFields.add("tenant"); + SortableFields.put("id", "a.id"); SortableFields.put("insertInstant", "a.insert_instant"); SortableFields.put("name", "a.name"); From 1a21610eff2294af3fbe48aa4697f03ae56b6cfd Mon Sep 17 00:00:00 2001 From: Lyle Schemmerling Date: Thu, 26 Jun 2025 16:41:36 -0600 Subject: [PATCH 2/4] add the application-tenant api and move it out of the application object --- .../fusionauth/client/FusionAuthClient.java | 70 ++++++++++++++++++- .../io/fusionauth/domain/Application.java | 8 +-- .../domain/UniversalApplicationTenant.java | 10 ++- .../UniversalApplicationTenantsRequest.java | 44 ++++++++++++ .../UniversalApplicationTenantsResponse.java | 43 ++++++++++++ 5 files changed, 165 insertions(+), 10 deletions(-) create mode 100644 src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantsRequest.java create mode 100644 src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantsResponse.java diff --git a/src/main/java/io/fusionauth/client/FusionAuthClient.java b/src/main/java/io/fusionauth/client/FusionAuthClient.java index d6c1a6c5..546f9df6 100644 --- a/src/main/java/io/fusionauth/client/FusionAuthClient.java +++ b/src/main/java/io/fusionauth/client/FusionAuthClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2023, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -145,6 +145,8 @@ import io.fusionauth.domain.api.TwoFactorRecoveryCodeResponse; import io.fusionauth.domain.api.TwoFactorRequest; import io.fusionauth.domain.api.TwoFactorResponse; +import io.fusionauth.domain.api.UniversalApplicationTenantsRequest; +import io.fusionauth.domain.api.UniversalApplicationTenantsResponse; import io.fusionauth.domain.api.UserActionReasonRequest; import io.fusionauth.domain.api.UserActionReasonResponse; import io.fusionauth.domain.api.UserActionRequest; @@ -982,6 +984,23 @@ public ClientResponse createTheme(UUID themeId, ThemeRequ .go(); } + /** + * Adds the application tenants for universal applications. + * + * @param applicationId The Id of the application that the role belongs to. + * @param request The request object that contains all the information used to create the Entity. + * @return The ClientResponse object. + */ + public ClientResponse createUniversalApplicationTenants(UUID applicationId, UniversalApplicationTenantsRequest request) { + return start(UniversalApplicationTenantsResponse.class, Errors.class) + .uri("/api/application") + .urlSegment(applicationId) + .urlSegment("application-tenant") + .bodyHandler(new JSONBodyHandler(request, objectMapper())) + .post() + .go(); + } + /** * Creates a user. You can optionally specify an Id for the user, if not provided one will be generated. * @@ -1577,6 +1596,40 @@ public ClientResponse deleteTheme(UUID themeId) { .go(); } + /** + * Removes the specified tenant from the universal application tenants list. + * + * @param applicationId The Id of the application that the role belongs to. + * @param tenantId The Id of the tenant to delete from the universal application tenants list. + * @return The ClientResponse object. + */ + public ClientResponse deleteUniversalApplicationTenant(UUID applicationId, UUID tenantId) { + return start(Void.TYPE, Errors.class) + .uri("/api/application") + .urlSegment(applicationId) + .urlSegment("application-tenant") + .urlSegment(tenantId) + .delete() + .go(); + } + + /** + * Removes the specified tenants from the universal application tenants list. + * + * @param applicationId The Id of the universal application that the tenants are linked to. + * @param tenantIds The Ids of the tenants to delete from the universal application tenants list. + * @return The ClientResponse object. + */ + public ClientResponse deleteUniversalApplicationTenants(UUID applicationId, Collection tenantIds) { + return start(Void.TYPE, Errors.class) + .uri("/api/application") + .urlSegment(applicationId) + .urlSegment("application-tenant") + .urlParameter("tenantIds", tenantIds) + .delete() + .go(); + } + /** * Deletes the user for the given Id. This permanently deletes all information, metrics, reports and data associated * with the user. @@ -4048,6 +4101,21 @@ public ClientResponse retrieveTwoFactorStatus(U .go(); } + /** + * Retrieves the application tenants for universal applications. + * + * @param applicationId The Id of the application that the role belongs to. + * @return The ClientResponse object. + */ + public ClientResponse retrieveUniversalApplicationTenants(UUID applicationId) { + return start(UniversalApplicationTenantsResponse.class, Errors.class) + .uri("/api/application") + .urlSegment(applicationId) + .urlSegment("application-tenant") + .get() + .go(); + } + /** * Retrieves the user for the given Id. * diff --git a/src/main/java/io/fusionauth/domain/Application.java b/src/main/java/io/fusionauth/domain/Application.java index 1dac51ce..0e1fbf05 100644 --- a/src/main/java/io/fusionauth/domain/Application.java +++ b/src/main/java/io/fusionauth/domain/Application.java @@ -892,9 +892,6 @@ public String toString() { public static class UniversalConfiguration { - // List of tenants allowed to use the universal application - public List applicationTenants = new ArrayList<>(); - // This is a flag to indicate that all tenants can use this universal application public boolean global; @@ -906,7 +903,6 @@ public UniversalConfiguration() { } public UniversalConfiguration(UniversalConfiguration other) { - this.applicationTenants = other.applicationTenants.stream().map(UniversalApplicationTenant::new).collect(Collectors.toList()); this.global = other.global; this.universal = other.universal; } @@ -917,12 +913,12 @@ public boolean equals(Object o) { return false; } UniversalConfiguration that = (UniversalConfiguration) o; - return universal == that.universal && Objects.equals(applicationTenants, that.applicationTenants); + return global == that.global && universal == that.universal; } @Override public int hashCode() { - return Objects.hash(applicationTenants, universal); + return Objects.hash(global, universal); } } } diff --git a/src/main/java/io/fusionauth/domain/UniversalApplicationTenant.java b/src/main/java/io/fusionauth/domain/UniversalApplicationTenant.java index 1e9e9002..b4552209 100644 --- a/src/main/java/io/fusionauth/domain/UniversalApplicationTenant.java +++ b/src/main/java/io/fusionauth/domain/UniversalApplicationTenant.java @@ -24,19 +24,23 @@ * @author Lyle Schemmerling */ public class UniversalApplicationTenant implements Buildable { + public UUID applicationId; + public UUID tenantId; @JacksonConstructor public UniversalApplicationTenant() { } - public UniversalApplicationTenant(UUID tenantId) { + public UniversalApplicationTenant(UUID tenantId, UUID applicationId) { this.tenantId = tenantId; + this.applicationId = applicationId; } // copy constructor public UniversalApplicationTenant(UniversalApplicationTenant other) { this.tenantId = other.tenantId; + this.applicationId = other.applicationId; } @Override @@ -45,11 +49,11 @@ public boolean equals(Object o) { return false; } UniversalApplicationTenant that = (UniversalApplicationTenant) o; - return Objects.equals(tenantId, that.tenantId); + return Objects.equals(tenantId, that.tenantId) && Objects.equals(applicationId, that.applicationId); } @Override public int hashCode() { - return Objects.hashCode(tenantId); + return Objects.hash(tenantId, applicationId); } } diff --git a/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantsRequest.java b/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantsRequest.java new file mode 100644 index 00000000..4195a175 --- /dev/null +++ b/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantsRequest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025, FusionAuth, All Rights Reserved + * + * 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. + */ +package io.fusionauth.domain.api; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import io.fusionauth.domain.Buildable; +import io.fusionauth.domain.UniversalApplicationTenant; + +/** + * @author Lyle Schemmerling + */ +public class UniversalApplicationTenantsRequest implements Buildable { + public List applicationTenants = new ArrayList<>(); + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + UniversalApplicationTenantsRequest that = (UniversalApplicationTenantsRequest) o; + return Objects.equals(applicationTenants, that.applicationTenants); + } + + @Override + public int hashCode() { + return Objects.hashCode(applicationTenants); + } +} diff --git a/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantsResponse.java b/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantsResponse.java new file mode 100644 index 00000000..c1895cff --- /dev/null +++ b/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantsResponse.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025, FusionAuth, All Rights Reserved + * + * 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. + */ +package io.fusionauth.domain.api; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import io.fusionauth.domain.UniversalApplicationTenant; + +/** + * @author Lyle Schemmerling + */ +public class UniversalApplicationTenantsResponse { + public List applicationTenants = new ArrayList<>(); + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + UniversalApplicationTenantsResponse that = (UniversalApplicationTenantsResponse) o; + return Objects.equals(applicationTenants, that.applicationTenants); + } + + @Override + public int hashCode() { + return Objects.hashCode(applicationTenants); + } +} From d85b1b4a0d017cff75a5376d08dff0b4e93609a5 Mon Sep 17 00:00:00 2001 From: Lyle Schemmerling Date: Mon, 7 Jul 2025 15:14:17 -0600 Subject: [PATCH 3/4] separate out the universal config and update clients --- .../fusionauth/client/FusionAuthClient.java | 77 ++++++++++++++----- .../io/fusionauth/domain/Application.java | 35 +-------- .../domain/UniversalApplicationTenant.java | 31 ++++++-- ...=> UniversalApplicationTenantRequest.java} | 24 ++++-- ...> UniversalApplicationTenantResponse.java} | 24 ++++-- ...iversalApplicationTenantSearchRequest.java | 53 +++++++++++++ ...versalApplicationTenantSearchResponse.java | 58 ++++++++++++++ 7 files changed, 229 insertions(+), 73 deletions(-) rename src/main/java/io/fusionauth/domain/api/{UniversalApplicationTenantsResponse.java => UniversalApplicationTenantRequest.java} (53%) rename src/main/java/io/fusionauth/domain/api/{UniversalApplicationTenantsRequest.java => UniversalApplicationTenantResponse.java} (57%) create mode 100644 src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantSearchRequest.java create mode 100644 src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantSearchResponse.java diff --git a/src/main/java/io/fusionauth/client/FusionAuthClient.java b/src/main/java/io/fusionauth/client/FusionAuthClient.java index 546f9df6..0be32134 100644 --- a/src/main/java/io/fusionauth/client/FusionAuthClient.java +++ b/src/main/java/io/fusionauth/client/FusionAuthClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2023, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -145,8 +145,10 @@ import io.fusionauth.domain.api.TwoFactorRecoveryCodeResponse; import io.fusionauth.domain.api.TwoFactorRequest; import io.fusionauth.domain.api.TwoFactorResponse; -import io.fusionauth.domain.api.UniversalApplicationTenantsRequest; -import io.fusionauth.domain.api.UniversalApplicationTenantsResponse; +import io.fusionauth.domain.api.UniversalApplicationTenantRequest; +import io.fusionauth.domain.api.UniversalApplicationTenantResponse; +import io.fusionauth.domain.api.UniversalApplicationTenantSearchRequest; +import io.fusionauth.domain.api.UniversalApplicationTenantSearchResponse; import io.fusionauth.domain.api.UserActionReasonRequest; import io.fusionauth.domain.api.UserActionReasonResponse; import io.fusionauth.domain.api.UserActionRequest; @@ -987,15 +989,17 @@ public ClientResponse createTheme(UUID themeId, ThemeRequ /** * Adds the application tenants for universal applications. * - * @param applicationId The Id of the application that the role belongs to. - * @param request The request object that contains all the information used to create the Entity. + * @param applicationId The Id of the application that the universal application tenant belongs to. + * @param universalApplicationTenantId (Optional) The Id of the universal application tenant. + * @param request The request object that contains all the information used to create the UniversalApplicationTenants. * @return The ClientResponse object. */ - public ClientResponse createUniversalApplicationTenants(UUID applicationId, UniversalApplicationTenantsRequest request) { - return start(UniversalApplicationTenantsResponse.class, Errors.class) + public ClientResponse createUniversalApplicationTenant(UUID applicationId, UUID universalApplicationTenantId, UniversalApplicationTenantRequest request) { + return start(UniversalApplicationTenantResponse.class, Errors.class) .uri("/api/application") .urlSegment(applicationId) - .urlSegment("application-tenant") + .urlSegment("universal-application-tenant") + .urlSegment(universalApplicationTenantId) .bodyHandler(new JSONBodyHandler(request, objectMapper())) .post() .go(); @@ -1597,18 +1601,18 @@ public ClientResponse deleteTheme(UUID themeId) { } /** - * Removes the specified tenant from the universal application tenants list. + * Deletes the universal application tenant. * - * @param applicationId The Id of the application that the role belongs to. - * @param tenantId The Id of the tenant to delete from the universal application tenants list. + * @param applicationId The Id of the application that the UniversalApplicationTenant belongs to. + * @param universalApplicationTenantId The Id of the UniversalApplicationTenant to delete. * @return The ClientResponse object. */ - public ClientResponse deleteUniversalApplicationTenant(UUID applicationId, UUID tenantId) { + public ClientResponse deleteUniversalApplicationTenant(UUID applicationId, UUID universalApplicationTenantId) { return start(Void.TYPE, Errors.class) .uri("/api/application") .urlSegment(applicationId) - .urlSegment("application-tenant") - .urlSegment(tenantId) + .urlSegment("universal-application-tenant") + .urlSegment(universalApplicationTenantId) .delete() .go(); } @@ -4102,16 +4106,18 @@ public ClientResponse retrieveTwoFactorStatus(U } /** - * Retrieves the application tenants for universal applications. + * Retrieves the universal application tenant. * - * @param applicationId The Id of the application that the role belongs to. + * @param applicationId The Id of the universal application that tenant is mapped to + * @param universalApplicationTenantId The Id of the universal application tenant. * @return The ClientResponse object. */ - public ClientResponse retrieveUniversalApplicationTenants(UUID applicationId) { - return start(UniversalApplicationTenantsResponse.class, Errors.class) + public ClientResponse retrieveUniversalApplicationTenant(UUID applicationId, UUID universalApplicationTenantId) { + return start(UniversalApplicationTenantResponse.class, Errors.class) .uri("/api/application") .urlSegment(applicationId) .urlSegment("application-tenant") + .urlSegment(universalApplicationTenantId) .get() .go(); } @@ -4950,6 +4956,22 @@ public ClientResponse searchThemes(ThemeSearchReque .go(); } + /** + * Searches universal application tenants for the specified applicationId and with the specified criteria and pagination. + * + * @param request The search criteria and pagination information. + * @return The ClientResponse object. + */ + public ClientResponse searchUniversalApplicationTenants(UniversalApplicationTenantSearchRequest request) { + return start(UniversalApplicationTenantSearchResponse.class, Errors.class) + .uri("/api/application") + .urlSegment("universal-application-tenant") + .urlSegment("search") + .bodyHandler(new JSONBodyHandler(request, objectMapper())) + .post() + .go(); + } + /** * Searches user comments with the specified criteria and pagination. * @@ -5671,6 +5693,25 @@ public ClientResponse updateTheme(UUID themeId, ThemeRequ .go(); } + /** + * Adds the application tenants for universal applications. + * + * @param applicationId The Id of the application that the UniversalApplicationTenant belongs to. + * @param universalApplicationTenantId The Id of the universal application tenant. + * @param request The request object that contains all the information used to create the UniversalApplicationTenant. + * @return The ClientResponse object. + */ + public ClientResponse updateUniversalApplicationTenant(UUID applicationId, UUID universalApplicationTenantId, UniversalApplicationTenantRequest request) { + return start(UniversalApplicationTenantResponse.class, Errors.class) + .uri("/api/application") + .urlSegment(applicationId) + .urlSegment("universal-application-tenant") + .urlSegment(universalApplicationTenantId) + .bodyHandler(new JSONBodyHandler(request, objectMapper())) + .put() + .go(); + } + /** * Updates the user with the given Id. * diff --git a/src/main/java/io/fusionauth/domain/Application.java b/src/main/java/io/fusionauth/domain/Application.java index 0e1fbf05..b1bb38b2 100644 --- a/src/main/java/io/fusionauth/domain/Application.java +++ b/src/main/java/io/fusionauth/domain/Application.java @@ -104,7 +104,7 @@ public class Application implements Buildable, Tenantable { public UUID themeId; - public UniversalConfiguration universalConfiguration = new UniversalConfiguration(); + public UniversalApplicationConfiguration universalConfiguration = new UniversalApplicationConfiguration(); public RegistrationUnverifiedOptions unverified = new RegistrationUnverifiedOptions(); @@ -149,7 +149,7 @@ public Application(Application other) { this.state = other.state; this.tenantId = other.tenantId; this.themeId = other.themeId; - this.universalConfiguration = new UniversalConfiguration(other.universalConfiguration); + this.universalConfiguration = new UniversalApplicationConfiguration(other.universalConfiguration); this.unverified = new RegistrationUnverifiedOptions(other.unverified); this.verificationEmailTemplateId = other.verificationEmailTemplateId; this.verificationStrategy = other.verificationStrategy; @@ -890,35 +890,4 @@ public String toString() { } } - public static class UniversalConfiguration { - - // This is a flag to indicate that all tenants can use this universal application - public boolean global; - - // This is a flag to indicate that this application is universal and can be used by the configured application tenants - public boolean universal; - - @JacksonConstructor - public UniversalConfiguration() { - } - - public UniversalConfiguration(UniversalConfiguration other) { - this.global = other.global; - this.universal = other.universal; - } - - @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) { - return false; - } - UniversalConfiguration that = (UniversalConfiguration) o; - return global == that.global && universal == that.universal; - } - - @Override - public int hashCode() { - return Objects.hash(global, universal); - } - } } diff --git a/src/main/java/io/fusionauth/domain/UniversalApplicationTenant.java b/src/main/java/io/fusionauth/domain/UniversalApplicationTenant.java index b4552209..4b48bb46 100644 --- a/src/main/java/io/fusionauth/domain/UniversalApplicationTenant.java +++ b/src/main/java/io/fusionauth/domain/UniversalApplicationTenant.java @@ -15,32 +15,44 @@ */ package io.fusionauth.domain; +import java.time.ZonedDateTime; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.Objects; import java.util.UUID; import com.inversoft.json.JacksonConstructor; /** + * An object that represents the mapping between a Universal Application and a Tenant. + * * @author Lyle Schemmerling */ public class UniversalApplicationTenant implements Buildable { public UUID applicationId; + public Map data = new LinkedHashMap<>(); + + public UUID id; + + public ZonedDateTime insertInstant; + + public ZonedDateTime lastUpdateInstant; + public UUID tenantId; @JacksonConstructor public UniversalApplicationTenant() { } - public UniversalApplicationTenant(UUID tenantId, UUID applicationId) { - this.tenantId = tenantId; - this.applicationId = applicationId; - } - // copy constructor public UniversalApplicationTenant(UniversalApplicationTenant other) { + this.id = other.id; this.tenantId = other.tenantId; this.applicationId = other.applicationId; + this.insertInstant = other.insertInstant; + this.lastUpdateInstant = other.lastUpdateInstant; + this.data = new LinkedHashMap<>(other.data); } @Override @@ -49,11 +61,16 @@ public boolean equals(Object o) { return false; } UniversalApplicationTenant that = (UniversalApplicationTenant) o; - return Objects.equals(tenantId, that.tenantId) && Objects.equals(applicationId, that.applicationId); + return Objects.equals(id, that.id) + && Objects.equals(insertInstant, that.insertInstant) + && Objects.equals(lastUpdateInstant, that.lastUpdateInstant) + && Objects.equals(applicationId, that.applicationId) + && Objects.equals(tenantId, that.tenantId) + && Objects.equals(data, that.data); } @Override public int hashCode() { - return Objects.hash(tenantId, applicationId); + return Objects.hash(id, insertInstant, lastUpdateInstant, applicationId, tenantId, data); } } diff --git a/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantsResponse.java b/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantRequest.java similarity index 53% rename from src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantsResponse.java rename to src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantRequest.java index c1895cff..548f3129 100644 --- a/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantsResponse.java +++ b/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantRequest.java @@ -15,29 +15,39 @@ */ package io.fusionauth.domain.api; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; +import com.inversoft.json.JacksonConstructor; +import io.fusionauth.domain.Buildable; import io.fusionauth.domain.UniversalApplicationTenant; /** + * The request object for creating or updating a Universal Application Tenant. + * * @author Lyle Schemmerling */ -public class UniversalApplicationTenantsResponse { - public List applicationTenants = new ArrayList<>(); +public class UniversalApplicationTenantRequest implements Buildable { + public UniversalApplicationTenant universalApplicationTenant; + + @JacksonConstructor + public UniversalApplicationTenantRequest() { + } + + public UniversalApplicationTenantRequest(UniversalApplicationTenant universalApplicationTenant) { + this.universalApplicationTenant = universalApplicationTenant; + } @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - UniversalApplicationTenantsResponse that = (UniversalApplicationTenantsResponse) o; - return Objects.equals(applicationTenants, that.applicationTenants); + UniversalApplicationTenantRequest that = (UniversalApplicationTenantRequest) o; + return Objects.equals(universalApplicationTenant, that.universalApplicationTenant); } @Override public int hashCode() { - return Objects.hashCode(applicationTenants); + return Objects.hashCode(universalApplicationTenant); } } diff --git a/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantsRequest.java b/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantResponse.java similarity index 57% rename from src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantsRequest.java rename to src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantResponse.java index 4195a175..8074fd21 100644 --- a/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantsRequest.java +++ b/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantResponse.java @@ -15,30 +15,38 @@ */ package io.fusionauth.domain.api; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; -import io.fusionauth.domain.Buildable; +import com.inversoft.json.JacksonConstructor; import io.fusionauth.domain.UniversalApplicationTenant; /** + * The response object for a single Universal Application Tenant. + * * @author Lyle Schemmerling */ -public class UniversalApplicationTenantsRequest implements Buildable { - public List applicationTenants = new ArrayList<>(); +public class UniversalApplicationTenantResponse { + public UniversalApplicationTenant universalApplicationTenant; + + @JacksonConstructor + public UniversalApplicationTenantResponse() { + } + + public UniversalApplicationTenantResponse(UniversalApplicationTenant universalApplicationTenant) { + this.universalApplicationTenant = universalApplicationTenant; + } @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - UniversalApplicationTenantsRequest that = (UniversalApplicationTenantsRequest) o; - return Objects.equals(applicationTenants, that.applicationTenants); + UniversalApplicationTenantResponse that = (UniversalApplicationTenantResponse) o; + return Objects.equals(universalApplicationTenant, that.universalApplicationTenant); } @Override public int hashCode() { - return Objects.hashCode(applicationTenants); + return Objects.hashCode(universalApplicationTenant); } } diff --git a/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantSearchRequest.java b/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantSearchRequest.java new file mode 100644 index 00000000..ec9ddaae --- /dev/null +++ b/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantSearchRequest.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025, FusionAuth, All Rights Reserved + * + * 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. + */ +package io.fusionauth.domain.api; + +import java.util.Objects; + +import com.inversoft.json.JacksonConstructor; +import io.fusionauth.domain.Buildable; +import io.fusionauth.domain.search.UniversalApplicationTenantSearchCriteria; + +/** + * The request object with the search criteria for Universal Application Tenants. + * + * @author Lyle Schemmerling + */ +public class UniversalApplicationTenantSearchRequest implements Buildable { + public UniversalApplicationTenantSearchCriteria search = new UniversalApplicationTenantSearchCriteria(); + + @JacksonConstructor + public UniversalApplicationTenantSearchRequest() { + } + + public UniversalApplicationTenantSearchRequest(UniversalApplicationTenantSearchCriteria search) { + this.search = search; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + UniversalApplicationTenantSearchRequest that = (UniversalApplicationTenantSearchRequest) o; + return Objects.equals(search, that.search); + } + + @Override + public int hashCode() { + return Objects.hashCode(search); + } +} diff --git a/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantSearchResponse.java b/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantSearchResponse.java new file mode 100644 index 00000000..ae138e87 --- /dev/null +++ b/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantSearchResponse.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025, FusionAuth, All Rights Reserved + * + * 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. + */ +package io.fusionauth.domain.api; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import com.inversoft.json.JacksonConstructor; +import io.fusionauth.domain.UniversalApplicationTenant; +import io.fusionauth.domain.search.SearchResults; + +/** + * The response object for Universal Application Tenants search results. + * + * @author Lyle Schemmerling + */ +public class UniversalApplicationTenantSearchResponse { + public List universalApplicationTenants = new ArrayList<>(); + + public long total; + + @JacksonConstructor + public UniversalApplicationTenantSearchResponse() { + } + + public UniversalApplicationTenantSearchResponse(SearchResults results) { + this.universalApplicationTenants = results.results; + this.total = results.total; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + UniversalApplicationTenantSearchResponse that = (UniversalApplicationTenantSearchResponse) o; + return total == that.total && Objects.equals(universalApplicationTenants, that.universalApplicationTenants); + } + + @Override + public int hashCode() { + return Objects.hash(universalApplicationTenants, total); + } +} From a38ed0561b6b1862c2c356a20ab8dab2348b92dc Mon Sep 17 00:00:00 2001 From: Lyle Schemmerling Date: Mon, 7 Jul 2025 15:25:36 -0600 Subject: [PATCH 4/4] add untracked files --- .../UniversalApplicationConfiguration.java | 55 ++++++++++++ ...versalApplicationTenantSearchCriteria.java | 83 +++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 src/main/java/io/fusionauth/domain/UniversalApplicationConfiguration.java create mode 100644 src/main/java/io/fusionauth/domain/search/UniversalApplicationTenantSearchCriteria.java diff --git a/src/main/java/io/fusionauth/domain/UniversalApplicationConfiguration.java b/src/main/java/io/fusionauth/domain/UniversalApplicationConfiguration.java new file mode 100644 index 00000000..4fb083bc --- /dev/null +++ b/src/main/java/io/fusionauth/domain/UniversalApplicationConfiguration.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, FusionAuth, All Rights Reserved + * + * 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. + */ +package io.fusionauth.domain; + +import java.util.Objects; + +import com.inversoft.json.JacksonConstructor; + +/** + * @author Lyle Schemmerling + */ +public class UniversalApplicationConfiguration { // TODO move to sep file/class and change name to UniversalApplicationConfiguration + + // This is a flag to indicate that all tenants can use this universal application + public boolean global; + + // This is a flag to indicate that this application is universal and can be used by the configured application tenants + public boolean universal; + + @JacksonConstructor + public UniversalApplicationConfiguration() { + } + + public UniversalApplicationConfiguration(UniversalApplicationConfiguration other) { + this.global = other.global; + this.universal = other.universal; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + UniversalApplicationConfiguration that = (UniversalApplicationConfiguration) o; + return global == that.global && universal == that.universal; + } + + @Override + public int hashCode() { + return Objects.hash(global, universal); + } +} diff --git a/src/main/java/io/fusionauth/domain/search/UniversalApplicationTenantSearchCriteria.java b/src/main/java/io/fusionauth/domain/search/UniversalApplicationTenantSearchCriteria.java new file mode 100644 index 00000000..1048e43d --- /dev/null +++ b/src/main/java/io/fusionauth/domain/search/UniversalApplicationTenantSearchCriteria.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2025, FusionAuth, All Rights Reserved + * + * 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. + */ +package io.fusionauth.domain.search; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +import io.fusionauth.domain.Buildable; +import static io.fusionauth.domain.util.SQLTools.normalizeOrderBy; +import static io.fusionauth.domain.util.SQLTools.toSearchString; + +/** + * @author Lyle Schemmerling + */ +public class UniversalApplicationTenantSearchCriteria extends BaseSearchCriteria implements Buildable { + public static final Map SortableFields = new LinkedHashMap<>(); + + public UUID applicationId; + + public UUID tenantId; + + public String tenantName; + + @Override + public BaseSearchCriteria prepare() { + if (orderBy == null) { + orderBy = defaultOrderBy(); + } + + orderBy = normalizeOrderBy(orderBy, SortableFields, Set.of()); + tenantName = toSearchString(tenantName); + return this; + } + + @Override + public Set supportedOrderByColumns() { + return SortableFields.keySet(); + } + + @Override + protected String defaultOrderBy() { + return "id ASC"; + } + + static { + SortableFields.put("id", "uat.id"); + SortableFields.put("insertInstant", "uat.insert_instant"); + SortableFields.put("lastUpdateInstant", "uat.last_update_instant"); + SortableFields.put("applicationId", "uat.applications_id"); + SortableFields.put("tenantId", "uat.tenants_id"); + SortableFields.put("tenantName", "t.name"); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + UniversalApplicationTenantSearchCriteria that = (UniversalApplicationTenantSearchCriteria) o; + return Objects.equals(applicationId, that.applicationId) && Objects.equals(tenantId, that.tenantId) && Objects.equals(tenantName, that.tenantName); + } + + @Override + public int hashCode() { + return Objects.hash(applicationId, tenantId, tenantName); + } +}