From f37b1be7e039f15ca2420294166cdfd4879e6bb5 Mon Sep 17 00:00:00 2001 From: josegar74 Date: Mon, 18 May 2026 12:51:00 +0200 Subject: [PATCH 1/2] Don't allow to create new users when using external authentication systems like KeyCloak or OpenID --- .../java/org/fao/geonet/util/XslUtil.java | 15 ++++ .../java/org/fao/geonet/util/XslUtil.java | 4 + .../org/fao/geonet/api/users/UsersApi.java | 84 +++++++++---------- .../resources/catalog/js/CatController.js | 2 + .../templates/admin/usergroup/users.html | 3 +- .../webapp/xslt/base-layout-cssjs-loader.xsl | 1 + .../webapp/xslt/common/base-variables.xsl | 3 + 7 files changed, 65 insertions(+), 47 deletions(-) diff --git a/core/src/main/java/org/fao/geonet/util/XslUtil.java b/core/src/main/java/org/fao/geonet/util/XslUtil.java index 4e8d5bb0e78a..fb6e64c980db 100644 --- a/core/src/main/java/org/fao/geonet/util/XslUtil.java +++ b/core/src/main/java/org/fao/geonet/util/XslUtil.java @@ -572,6 +572,21 @@ public static boolean isDisableLoginForm() { return false; } + /** + * Check if user profile create is enabled. + */ + public static boolean isUserProfileCreateEnabled() { + SecurityProviderConfiguration securityProviderConfiguration = SecurityProviderConfiguration.get(); + + if (securityProviderConfiguration != null) { + // No user creation allowed if providing a link or autologin + return !securityProviderConfiguration.getLoginType().equals(SecurityProviderConfiguration.LoginType.AUTOLOGIN.toString().toLowerCase()) + && !securityProviderConfiguration.getLoginType().equals(SecurityProviderConfiguration.LoginType.LINK.toString().toLowerCase()); + } + // If we cannot find SecurityProviderConfiguration then default to true. + return true; + } + /** * Check if security provider require login link */ diff --git a/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/util/XslUtil.java b/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/util/XslUtil.java index 65c5e727d4ab..fa07b96f643e 100644 --- a/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/util/XslUtil.java +++ b/schemas/iso19115-3.2018/src/test/java/org/fao/geonet/util/XslUtil.java @@ -117,6 +117,10 @@ public static boolean isShowLoginAsLink() { return false; } + public static boolean isUserProfileCreateEnabled() { + return true; + } + public static boolean isUserProfileUpdateEnabled() { return true; } diff --git a/services/src/main/java/org/fao/geonet/api/users/UsersApi.java b/services/src/main/java/org/fao/geonet/api/users/UsersApi.java index 772dbf28a896..e6bcf1317417 100644 --- a/services/src/main/java/org/fao/geonet/api/users/UsersApi.java +++ b/services/src/main/java/org/fao/geonet/api/users/UsersApi.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2001-2025 Food and Agriculture Organization of the + * Copyright (C) 2001-2026 Food and Agriculture Organization of the * United Nations (FAO-UN), United Nations World Food Programme (WFP) * and United Nations Environment Programme (UNEP) * @@ -45,9 +45,9 @@ import org.fao.geonet.domain.*; import org.fao.geonet.domain.auditable.UserAuditable; import org.fao.geonet.exceptions.UserNotFoundEx; -import org.fao.geonet.kernel.DataManager; +import org.fao.geonet.kernel.datamanager.IMetadataOperations; +import org.fao.geonet.kernel.datamanager.IMetadataStatus; import org.fao.geonet.kernel.datamanager.IMetadataUtils; -import org.fao.geonet.kernel.datamanager.base.BaseMetadataStatus; import org.fao.geonet.kernel.security.SecurityProviderConfiguration; import org.fao.geonet.kernel.setting.SettingManager; import org.fao.geonet.repository.*; @@ -55,6 +55,7 @@ import org.fao.geonet.repository.specification.UserGroupSpecs; import org.fao.geonet.repository.specification.UserSpecs; import org.fao.geonet.util.PasswordUtil; +import org.fao.geonet.util.XslUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.support.ResourceBundleMessageSource; @@ -114,15 +115,9 @@ public class UsersApi { @Autowired UserGroupRepository userGroupRepository; - @Autowired - BaseMetadataStatus baseMetadataStatus; - @Autowired UserSavedSelectionRepository userSavedSelectionRepository; - @Autowired - DataManager dataManager; - @Autowired LanguageUtils languageUtils; @@ -139,7 +134,13 @@ public class UsersApi { @Autowired UserAuditableService userAuditableService; - private BufferedImage pixel; + @Autowired + private IMetadataOperations metadataOperations; + + @Autowired + private IMetadataStatus metadataStatus; + + private final BufferedImage pixel; public UsersApi() { pixel = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); @@ -148,8 +149,7 @@ public UsersApi() { @io.swagger.v3.oas.annotations.Operation( - summary = "Get users", - description = "") + summary = "Get users") @RequestMapping( produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET) @@ -159,16 +159,13 @@ public UsersApi() { public List getUsers( @Parameter(hidden = true) HttpSession httpSession - ) throws Exception { + ) { UserSession session = ApiUtils.getUserSession(httpSession); Profile profile = session.getProfile(); if (Profile.Administrator.equals(profile)) { // Get all users return userRepository.findAll(SortUtils.createSort(User_.name)); - } else if (!Profile.UserAdmin.equals(profile)) { - // Return only the current user - return userRepository.findAll(UserSpecs.hasUserId(session.getUserIdAsInt())); } else if (Profile.UserAdmin.equals(profile)) { // Return all the users belonging to a group where the current user is UserAdmin int userId = session.getUserIdAsInt(); @@ -186,15 +183,15 @@ public List getUsers( // alToRemove.add(elRec); return allUsers; + } else { + // Return only the current user + return userRepository.findAll(UserSpecs.hasUserId(session.getUserIdAsInt())); } - - return null; } @io.swagger.v3.oas.annotations.Operation( - summary = "Get user", - description = "") + summary = "Get user") @RequestMapping( value = "/{userIdentifier}", produces = MediaType.APPLICATION_JSON_VALUE, @@ -211,7 +208,7 @@ public User getUser( @Parameter(hidden = true) HttpSession httpSession - ) throws Exception { + ) { UserSession session = ApiUtils.getUserSession(httpSession); Profile myProfile = session.getProfile(); String myUserId = session.getUserId(); @@ -243,8 +240,7 @@ public User getUser( } @io.swagger.v3.oas.annotations.Operation( - summary = "Get user identicon", - description = "") + summary = "Get user identicon") @RequestMapping( value = "/{userIdentifier}.png", produces = MediaType.IMAGE_PNG_VALUE, @@ -321,7 +317,6 @@ public ResponseEntity deleteUser( Profile myProfile = session.getProfile(); String myUserId = session.getUserId(); - if (myUserId == null || myUserId.equals(Integer.toString(userIdentifier))) { throw new IllegalArgumentException( "You cannot delete yourself from the user database"); @@ -329,7 +324,7 @@ public ResponseEntity deleteUser( if (myProfile == Profile.UserAdmin) { - final Integer iMyUserId = Integer.parseInt(myUserId); + final int iMyUserId = Integer.parseInt(myUserId); final List groupIdsSessionUser = userGroupRepository .findGroupIds(where(hasUserId(iMyUserId))); @@ -344,7 +339,7 @@ public ResponseEntity deleteUser( // Before processing DELETE check that the user is not referenced // elsewhere in the GeoNetwork database - an exception is thrown if // this is the case - if (dataManager.isUserMetadataOwner(userIdentifier)) { + if (metadataOperations.isUserMetadataOwner(userIdentifier)) { IMetadataUtils metadataRepository = ApplicationContextHolder.get().getBean(IMetadataUtils.class); final long numUserRecords = metadataRepository.count(MetadataSpecs.isOwnedByUser(userIdentifier)); throw new IllegalArgumentException( @@ -353,10 +348,10 @@ public ResponseEntity deleteUser( numUserRecords)); } - if (dataManager.isUserMetadataStatus(userIdentifier)) { + if (metadataStatus.isUserMetadataStatus(userIdentifier)) { Optional nobody = userRepository.findById(0); if (nobody.isPresent()) { - baseMetadataStatus.transferMetadataStatusOwnership(userIdentifier, + metadataStatus.transferMetadataStatusOwnership(userIdentifier, nobody.get().getId()); } else { throw new IllegalArgumentException( @@ -386,15 +381,11 @@ public ResponseEntity deleteUser( } - return new ResponseEntity(HttpStatus.NO_CONTENT); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @io.swagger.v3.oas.annotations.Operation( - summary = "Check if a user property already exist", - description = "" - // authorizations = { - // @Authorization(value = "basicAuth") - // }) + summary = "Check if a user property already exist" ) @RequestMapping( value = "/properties/{property}", @@ -454,13 +445,17 @@ public ResponseEntity createUser( ServletRequest request, @Parameter(hidden = true) HttpSession httpSession - ) throws Exception { + ) { Locale locale = languageUtils.parseAcceptLanguage(request.getLocales()); ResourceBundle messages = ResourceBundle.getBundle("org.fao.geonet.api.Messages", locale); UserSession session = ApiUtils.getUserSession(httpSession); Profile myProfile = session.getProfile(); + if (!XslUtil.isUserProfileCreateEnabled()) { + return new ResponseEntity<>(messages.getString("security_provider_unsupported_functionality"), HttpStatus.PRECONDITION_FAILED); + } + // Allow administrator to modify the user profile as they may need to manually pre-create users via api in certain cases (i.e. migration) if (securityProviderConfiguration != null && !securityProviderConfiguration.isUserProfileUpdateEnabled() && @@ -535,7 +530,7 @@ public ResponseEntity createUser( UserAuditable userAuditable = UserAuditable.build(user, userGroups); userAuditableService.auditSave(userAuditable); - return new ResponseEntity(HttpStatus.NO_CONTENT); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @io.swagger.v3.oas.annotations.Operation( @@ -562,7 +557,7 @@ public ResponseEntity updateUser( ServletRequest request, @Parameter(hidden = true) HttpSession httpSession - ) throws Exception { + ) { Locale locale = languageUtils.parseAcceptLanguage(request.getLocales()); Profile profile = Profile.findProfileIgnoreCase(userDto.getProfile()); @@ -677,7 +672,7 @@ public ResponseEntity updateUser( UserAuditable userAuditable = UserAuditable.build(user, userGroups); userAuditableService.auditSave(userAuditable); - return new ResponseEntity(HttpStatus.NO_CONTENT); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); } private boolean isUserAllowedToResetWithoutOldPassword(Profile myProfile) { @@ -708,7 +703,7 @@ public ResponseEntity resetUserPassword( ServletRequest request, @Parameter(hidden = true) HttpSession httpSession - ) throws Exception { + ) { Locale locale = languageUtils.parseAcceptLanguage(request.getLocales()); ResourceBundle messages = ResourceBundle.getBundle("org.fao.geonet.api.Messages", locale); @@ -742,7 +737,7 @@ public ResponseEntity resetUserPassword( PasswordEncoder encoder = PasswordUtil.encoder(ApplicationContextHolder.get()); - if (isUserAllowedToResetWithoutOldPassword(myProfile) == false + if (!isUserAllowedToResetWithoutOldPassword(myProfile) && (passwordResetDto.getPasswordOld() == null || !encoder.matches( passwordResetDto.getPasswordOld(), @@ -756,7 +751,7 @@ public ResponseEntity resetUserPassword( user.get().getSecurity().getSecurityNotifications().remove(UserSecurityNotification.UPDATE_HASH_REQUIRED); userRepository.save(user.get()); - return new ResponseEntity(HttpStatus.NO_CONTENT); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @io.swagger.v3.oas.annotations.Operation( @@ -779,7 +774,7 @@ public List retrieveUserGroups( ServletRequest request, @Parameter(hidden = true) HttpSession httpSession - ) throws Exception { + ) { UserSession session = ApiUtils.getUserSession(httpSession); Profile myProfile = session.getProfile(); String myUserId = session.getUserId(); @@ -801,7 +796,7 @@ public List retrieveUserGroups( // Return all groups for administrator. // TODO: Check if a better option returning instead of UserGroup a customised GroupDTO // containing all group properties and user profile - userGroups = new ArrayList(); + userGroups = new ArrayList<>(); List groups = groupRepository.findAll(); @@ -850,8 +845,7 @@ private List getGroupIdsWhereUserIsUserAdmin(int userId) { } - private void setUserGroups(final User user, List userGroups, Locale locale) - throws Exception { + private void setUserGroups(final User user, List userGroups, Locale locale) { Collection all = userGroupRepository.findAll(UserGroupSpecs .hasUserId(user.getId())); diff --git a/web-ui/src/main/resources/catalog/js/CatController.js b/web-ui/src/main/resources/catalog/js/CatController.js index 1a3ea08ae3e6..e3d7d79072e2 100644 --- a/web-ui/src/main/resources/catalog/js/CatController.js +++ b/web-ui/src/main/resources/catalog/js/CatController.js @@ -1394,6 +1394,7 @@ current: null, isDisableLoginForm: false, isShowLoginAsLink: false, + isUserProfileCreateEnabled: true, isUserProfileUpdateEnabled: true, isUserGroupUpdateEnabled: true, init: function ( @@ -1804,6 +1805,7 @@ $scope.isDebug = window.location.search.indexOf("debug") !== -1; $scope.isDisableLoginForm = gnGlobalSettings.isDisableLoginForm; $scope.isShowLoginAsLink = gnGlobalSettings.isShowLoginAsLink; + $scope.isUserProfileCreateEnabled = gnGlobalSettings.isUserProfileCreateEnabled; $scope.isUserProfileUpdateEnabled = gnGlobalSettings.isUserProfileUpdateEnabled; $scope.isUserGroupUpdateEnabled = gnGlobalSettings.isUserGroupUpdateEnabled; $scope.isExternalViewerEnabled = gnExternalViewer.isEnabled(); diff --git a/web-ui/src/main/resources/catalog/templates/admin/usergroup/users.html b/web-ui/src/main/resources/catalog/templates/admin/usergroup/users.html index a4aa18bbee80..9994ea2c4cd0 100644 --- a/web-ui/src/main/resources/catalog/templates/admin/usergroup/users.html +++ b/web-ui/src/main/resources/catalog/templates/admin/usergroup/users.html @@ -42,13 +42,12 @@ data-cache="users" > -