Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.security.domain.OidcFederationType;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Getter
Expand Down Expand Up @@ -91,6 +92,8 @@ public class FineractProperties {

private RetryProperties retry;

private FineractDefaultValues defaults;

@Getter
@Setter
public static class FineractTenantProperties {
Expand Down Expand Up @@ -520,6 +523,7 @@ public static class FineractSecurityProperties {
private FineractSecurityTwoFactorAuth twoFactor;
private FineractSecurityHsts hsts;
private FineractSecurityOAuth2Properties oauth2;
private FineractSecurityOidcFederationProperties oidcFederation;
private CorsProperties cors;

public void set2fa(FineractSecurityTwoFactorAuth twoFactor) {
Expand Down Expand Up @@ -556,6 +560,45 @@ public static final class Registration implements Serializable {
}
}

@Getter
@Setter
public static class FineractSecurityOidcFederationProperties {
Copy link
Copy Markdown
Contributor

@adamsaghy adamsaghy May 25, 2026

Choose a reason for hiding this comment

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

This looks messy to me...
Why should we have any of the below fields as configuration:

  • tenantClaimName -> There should be no default value
  • usernameClaim -> There should be no default value
  • autoCreateUser -> There should be no such functionality as part of OIDC, maybe as a follow-up...
  • defaultRoles? -> No default roles... security nightmare AS-IS


private boolean enabled;
// JWT claim name used to resolve the Fineract tenant ID.
// Falls back to HTTP header / query param if absent.
private String tenantClaimName = "fineract_tenant";
// Claim used as the Fineract username. Common values: preferred_username, email, sub.
private String usernameClaim = "preferred_username";
// When true, creates a Fineract AppUser on first successful OIDC login.
private boolean autoCreateUser = false;
// Comma-separated role names assigned to auto-created users.
private String defaultRoles = "";
// Controls the RP-Initiated Logout URL format.
// Values: keycloak | azure_ad | okta | auth0 | generic (default)
private OidcFederationType provider = OidcFederationType.GENERIC;
// Redirect URI sent to the IdP after successful logout.
private String postLogoutRedirectUri;
// Static per-issuer tenant mapping (YAML fallback).
// Used when the master DB has no m_tenant_oidc_config record for an incoming issuer.
// Priority: DB config > issuers[] > tenantClaimName claim.
private List<OidcIssuerProperties> issuers = new ArrayList<>();

@Getter
@Setter
public static class OidcIssuerProperties {

// Exact value expected in the JWT 'iss' claim.
private String issuerUri;
// Fineract tenant identifier this issuer maps to.
private String tenantId;
// Optional: if absent, derived from issuerUri via OIDC discovery.
private String jwksUri;
// Optional: per-issuer override for the username claim.
private String usernameClaim;
}
}

@Getter
@Setter
public static class FineractSecurityBasicAuth {
Expand Down Expand Up @@ -702,4 +745,11 @@ public static class CorsProperties {
private List<String> exposedHeaders;
private boolean allowCredentials;
}

@Getter
@Setter
public static class FineractDefaultValues {

private Long officeId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.fineract.infrastructure.core.domain;

import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.apache.fineract.infrastructure.security.domain.OidcFederationType;

/**
* Holds the per-tenant OIDC/IdP configuration stored in the master database (m_tenant_oidc_config). One record per
* tenant; the issuerUri is the resolution key matched against the JWT 'iss' claim.
*/
@Builder
@Getter
@Setter
@EqualsAndHashCode(of = "issuerUri")
public class TenantOidcConfig {

private Long id;
private String tenantId;
private OidcFederationType providerType;
private String issuerUri;
private String clientId;
/** Stored AES-256-GCM encrypted; never exposed in API responses. */
private String clientSecret;
/** Optional: if null, derived from issuerUri via OIDC discovery. */
private String jwksUri;
private String usernameClaim;
private String scopes;
private String postLogoutRedirectUri;
private boolean enabled;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.fineract.infrastructure.security.domain;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.api.ApiFacingEnum;

@Getter
@RequiredArgsConstructor
public enum OidcFederationType implements ApiFacingEnum<OidcFederationType> {

GENERIC("generic", "Generic (default)"), AUTH0("auth0", "Auth0"), AZURE("azure_ad", "Azure AD"), GOOGLE("google",
"Google"), KEYCLOAK("keycloak", "Keycloak"), OKTA("okta", "Okta"),;

private final String code;
private final String humanReadableName;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.fineract.infrastructure.core.config;

import static org.assertj.core.api.Assertions.assertThat;

import org.apache.fineract.infrastructure.core.config.FineractProperties.FineractSecurityProperties.FineractSecurityOidcFederationProperties;
import org.junit.jupiter.api.Test;

class FineractPropertiesOidcTest {

@Test
void defaultEnabledIsFalse() {
assertThat(new FineractSecurityOidcFederationProperties().isEnabled()).isFalse();
}

@Test
void defaultUsernameClaimIsPreferredUsername() {
assertThat(new FineractSecurityOidcFederationProperties().getUsernameClaim()).isEqualTo("preferred_username");
}

@Test
void defaultTenantClaimName() {
assertThat(new FineractSecurityOidcFederationProperties().getTenantClaimName()).isEqualTo("fineract_tenant");
}

@Test
void defaultAutoCreateUserIsFalse() {
assertThat(new FineractSecurityOidcFederationProperties().isAutoCreateUser()).isFalse();
}

@Test
void defaultProviderIsGeneric() {
assertThat(new FineractSecurityOidcFederationProperties().getProvider().getCode()).isEqualTo("generic");
}

@Test
void defaultDefaultRolesIsEmpty() {
assertThat(new FineractSecurityOidcFederationProperties().getDefaultRoles()).isEmpty();
}

@Test
void defaultPostLogoutRedirectUriIsNull() {
assertThat(new FineractSecurityOidcFederationProperties().getPostLogoutRedirectUri()).isNull();
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions fineract-doc/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.Project Overview
* xref:index.adoc[Introduction]
* xref:faq.adoc[FAQs]

.Security & Configuration
* xref:oidc-federation.adoc[OIDC Federation]
** xref:oidc-federation-google.adoc[Google Identity Provider]
Loading
Loading