diff --git a/src/main/java/io/fusionauth/client/FusionAuthClient.java b/src/main/java/io/fusionauth/client/FusionAuthClient.java index d6c1a6c5..0be32134 100644 --- a/src/main/java/io/fusionauth/client/FusionAuthClient.java +++ b/src/main/java/io/fusionauth/client/FusionAuthClient.java @@ -145,6 +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.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; @@ -982,6 +986,25 @@ public ClientResponse createTheme(UUID themeId, ThemeRequ .go(); } + /** + * Adds the application tenants for universal applications. + * + * @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 createUniversalApplicationTenant(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())) + .post() + .go(); + } + /** * Creates a user. You can optionally specify an Id for the user, if not provided one will be generated. * @@ -1577,6 +1600,40 @@ public ClientResponse deleteTheme(UUID themeId) { .go(); } + /** + * Deletes the universal application tenant. + * + * @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 universalApplicationTenantId) { + return start(Void.TYPE, Errors.class) + .uri("/api/application") + .urlSegment(applicationId) + .urlSegment("universal-application-tenant") + .urlSegment(universalApplicationTenantId) + .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 +4105,23 @@ public ClientResponse retrieveTwoFactorStatus(U .go(); } + /** + * Retrieves the universal application tenant. + * + * @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 retrieveUniversalApplicationTenant(UUID applicationId, UUID universalApplicationTenantId) { + return start(UniversalApplicationTenantResponse.class, Errors.class) + .uri("/api/application") + .urlSegment(applicationId) + .urlSegment("application-tenant") + .urlSegment(universalApplicationTenantId) + .get() + .go(); + } + /** * Retrieves the user for the given Id. * @@ -4882,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. * @@ -5603,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 be19f1d7..b1bb38b2 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 UniversalApplicationConfiguration universalConfiguration = new UniversalApplicationConfiguration(); + 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 UniversalApplicationConfiguration(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,5 @@ public String toString() { } } } + } 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/UniversalApplicationTenant.java b/src/main/java/io/fusionauth/domain/UniversalApplicationTenant.java new file mode 100644 index 00000000..4b48bb46 --- /dev/null +++ b/src/main/java/io/fusionauth/domain/UniversalApplicationTenant.java @@ -0,0 +1,76 @@ +/* + * 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.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() { + } + + // 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 + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + UniversalApplicationTenant that = (UniversalApplicationTenant) o; + 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(id, insertInstant, lastUpdateInstant, applicationId, tenantId, data); + } +} diff --git a/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantRequest.java b/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantRequest.java new file mode 100644 index 00000000..548f3129 --- /dev/null +++ b/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantRequest.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.UniversalApplicationTenant; + +/** + * The request object for creating or updating a Universal Application Tenant. + * + * @author Lyle Schemmerling + */ +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; + } + UniversalApplicationTenantRequest that = (UniversalApplicationTenantRequest) o; + return Objects.equals(universalApplicationTenant, that.universalApplicationTenant); + } + + @Override + public int hashCode() { + return Objects.hashCode(universalApplicationTenant); + } +} diff --git a/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantResponse.java b/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantResponse.java new file mode 100644 index 00000000..8074fd21 --- /dev/null +++ b/src/main/java/io/fusionauth/domain/api/UniversalApplicationTenantResponse.java @@ -0,0 +1,52 @@ +/* + * 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.UniversalApplicationTenant; + +/** + * The response object for a single Universal Application Tenant. + * + * @author Lyle Schemmerling + */ +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; + } + UniversalApplicationTenantResponse that = (UniversalApplicationTenantResponse) o; + return Objects.equals(universalApplicationTenant, that.universalApplicationTenant); + } + + @Override + public int hashCode() { + 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); + } +} 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"); 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); + } +}