Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
35a2074
Add dashboard properties
SteDev2 Mar 3, 2026
5c4a02c
Refactor DashboardProperties
SteDev2 Mar 3, 2026
e355c12
Add dashboard properties validator
SteDev2 Mar 6, 2026
0f7a556
Adds dashboard enabled flag
SteDev2 Mar 9, 2026
bd9b92b
Fix typo
SteDev2 Mar 9, 2026
6f9f814
Check dashboard configuration record on DB
SteDev2 Mar 9, 2026
509bfe0
Limit max client secret length
SteDev2 Mar 9, 2026
e0f5a61
Removes dashboard client base URL from config
SteDev2 Mar 10, 2026
221e380
Add unit tests for DashboardConfigService
SteDev2 Mar 10, 2026
2368b2e
Add unit tests for DashboardConfigValidator
SteDev2 Mar 11, 2026
9a46b13
Update CLIENT_ID_REGEX to match UUID
SteDev2 Mar 11, 2026
707936d
Update DashboardConfigValidator
SteDev2 Mar 11, 2026
dfd8ec5
Fix scope for new DashboardClient
SteDev2 Mar 11, 2026
ce43a48
Add tests in DashboardConfigServiceTest
SteDev2 Mar 12, 2026
c8e445b
Merge branch 'develop' into init-dashboard
SteDev2 Mar 12, 2026
212ae55
Remove unused imports
SteDev2 Mar 12, 2026
68af9af
Add StartupRunnerTests to verify dashboard initialization
SteDev2 Mar 13, 2026
a76e6ec
Refactor StartupRunner
SteDev2 Mar 13, 2026
c78c608
Add @Transactional annotation
SteDev2 Mar 13, 2026
287f255
Refactor exception
SteDev2 Mar 13, 2026
2e58c58
Add StartupRunnerWithoutConfigurationTests
SteDev2 Mar 13, 2026
9824e29
Add tests for StartupRunner and refactor
SteDev2 Mar 13, 2026
aa9e232
Add mock behavior for clientService in StartupRunnerTests
SteDev2 Mar 13, 2026
2490e6b
Refactor and improve tests
SteDev2 Mar 16, 2026
c158b3b
Refactor dashboard configuration and update tests
SteDev2 Mar 20, 2026
48e0c96
Remove useless import
SteDev2 Mar 20, 2026
d4ad58e
Refactor variable naming
SteDev2 Mar 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

/**
* Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021
*
* 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 it.infn.mw.iam.api.client.management.validation;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import it.infn.mw.iam.config.IamProperties.DashboardProperties;

@Component
@Scope("prototype")
public class DashboardConfigValidator implements ConstraintValidator<ValidDashboard, DashboardProperties> {

private static final String CLIENT_ID_REGEX = "^[a-zA-Z0-9\\-._~]{4,256}$";
private static final String CLIENT_SECRET_REGEX = "^[a-zA-Z0-9\\-._~]{32,72}$";

@Override
public boolean isValid(DashboardProperties dashboardProperties, ConstraintValidatorContext context) {
if (dashboardProperties == null || !dashboardProperties.isEnabled()) {
return true;
}
boolean validClientId = dashboardProperties.getClientId() != null
&& dashboardProperties.getClientId().matches(CLIENT_ID_REGEX);
boolean validClientSecret = dashboardProperties.getClientSecret() != null
&& dashboardProperties.getClientSecret().matches(CLIENT_SECRET_REGEX);

return validClientId && validClientSecret;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021
*
* 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 it.infn.mw.iam.api.client.management.validation;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

@Retention(RUNTIME)
@Target({FIELD})
@Constraint(validatedBy = DashboardConfigValidator.class)
public @interface ValidDashboard {
String message() default "Invalid dashboard client configuration";

Class <?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class DefaultClientDefaultsService implements ClientDefaultsService {
EnumSet.of(AuthMethod.SECRET_BASIC, AuthMethod.SECRET_POST, AuthMethod.SECRET_JWT);

private static final int SECRET_SIZE = 512;
private static final int BCRYPT_MAX_SIZE = 72;
private static final SecureRandom RNG = new SecureRandom();

private final ClientRegistrationProperties properties;
Expand Down Expand Up @@ -95,9 +96,7 @@ public ClientDetailsEntity setupClientDefaults(ClientDetailsEntity client) {

@Override
public String generateClientSecret() {
return
Base64.encodeBase64URLSafeString(new BigInteger(SECRET_SIZE, RNG).toByteArray())
.replace("=", "");
return Base64.encodeBase64URLSafeString(new BigInteger(SECRET_SIZE, RNG).toByteArray()).substring(0, BCRYPT_MAX_SIZE);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,20 @@

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.google.common.collect.Lists;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWSAlgorithm;

import it.infn.mw.iam.api.client.management.validation.ValidDashboard;
import it.infn.mw.iam.authn.ExternalAuthenticationRegistrationInfo.ExternalAuthenticationType;
import it.infn.mw.iam.config.login.LoginButtonProperties;
import it.infn.mw.iam.config.multi_factor_authentication.VerifyButtonProperties;

@Component
@Validated
@ConfigurationProperties(prefix = "iam")
public class IamProperties {

Expand Down Expand Up @@ -599,6 +602,38 @@ public void setTrackLastUsed(boolean trackLastUsed) {
}
}

@Validated
public static class DashboardProperties {

private boolean enabled = false;
private String clientId;
private String clientSecret;

public boolean isEnabled() {
return enabled;
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public String getClientId() {
return clientId;
}

public void setClientId(String clientId) {
this.clientId = clientId;
}

public String getClientSecret() {
return clientSecret;
}

public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
}

public static class DefaultGroup {
private String name;
private String enrollment = "INSERT";
Expand Down Expand Up @@ -727,6 +762,9 @@ public void setUrnSubnamespaces(String urnSubnamespaces) {

private AarcProfile aarcProfile = new AarcProfile();

@ValidDashboard
private DashboardProperties dashboard = new DashboardProperties();

public String getBaseUrl() {
return baseUrl;
}
Expand Down Expand Up @@ -969,6 +1007,18 @@ public ClientProperties getClient() {
return client;
}

public DashboardProperties getDashboard() {
return dashboard;
}

public void setDashboard(DashboardProperties dashboard) {
this.dashboard = dashboard;
}

public Boolean isDashboardPropertiesEnable() {
return dashboard != null;
}

public AarcProfile getAarcProfile() {
return aarcProfile;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021
*
* 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 it.infn.mw.iam.core.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import it.infn.mw.iam.dashboard.DashboardConfigService;

@Component
public class StartupRunner implements ApplicationRunner {

private static final Logger LOG = LoggerFactory.getLogger(StartupRunner.class);

private final DashboardConfigService dashboardConfigService;

public StartupRunner(DashboardConfigService dashboardConfigService) {
this.dashboardConfigService = dashboardConfigService;
}

@Override
public void run(ApplicationArguments args) {
if (!dashboardConfigService.isEnabled()) {
LOG.info(
"Dashboard client is disabled, skipping checks for the dashboard client properties and the presence of the record for the dashboard client");
Copy link
Member

Choose a reason for hiding this comment

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

I'd simplify this message into "Skipping dashboard client initialization." The perfect message should include something like "Read more info about here: " + URL of an updated IAM documentation.

return;
}

boolean isValid = dashboardConfigService.init();
if (!isValid) {
Copy link
Member

Choose a reason for hiding this comment

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

checking the code of the service, I see false is returned after an Exception is thrown on db entry creation. I think we can raise another error here saying something like "Error during dashboard client initialization. Startup failed. Please check the logged error."

throw new IllegalStateException(
"Dashboard client record does not exist or is not valid. Please check the dashboard client properties and ensure that a record with the specified client id, client secret and redirect uri exists in the database");
}
}
}
Loading
Loading