From ed9e86436bf9d36303e9c2c3ce8f81447dd05ac0 Mon Sep 17 00:00:00 2001 From: Nadim Ritter Date: Wed, 20 Mar 2024 16:04:00 +0100 Subject: [PATCH] SEBSLI-9 store admin pw in db + expose endpoint in ssl mode + remove admin pw after pw change --- .../ethz/seb/sebserver/WebSecurityConfig.java | 1 + .../sebserver/gbl/model/user/UserRole.java | 8 ++ .../webservice/AdminUserInitializer.java | 44 ++++++++-- .../servicelayer/light/impl/LightInit.java | 5 +- ...htController.java => LightController.java} | 52 ++++++++++-- .../weblayer/api/UserAccountController.java | 82 +++++++++++++------ 6 files changed, 151 insertions(+), 41 deletions(-) rename src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/{ExamAPIDiscoveryLightController.java => LightController.java} (66%) diff --git a/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java b/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java index f8587bbb..9dd9029f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java @@ -85,6 +85,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements E .antMatchers(CHECK_PATH) .antMatchers(this.examAPIDiscoveryEndpoint) .antMatchers(this.examAPIDiscoveryEndpoint + API.EXAM_API_CONFIGURATION_LIGHT_ENDPOINT) + .antMatchers(this.examAPIDiscoveryEndpoint + API.EXAM_API_CONFIGURATION_LIGHT_ENDPOINT + API.PASSWORD_PATH_SEGMENT) .antMatchers(this.adminAPIEndpoint + API.INFO_ENDPOINT + API.LOGO_PATH_SEGMENT + "/**") .antMatchers(this.adminAPIEndpoint + API.INFO_ENDPOINT + API.INFO_INST_PATH_SEGMENT + "/**") .antMatchers(this.adminAPIEndpoint + API.REGISTER_ENDPOINT); diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserRole.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserRole.java index 506d4306..03bc5cba 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserRole.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserRole.java @@ -60,4 +60,12 @@ public enum UserRole implements Entity, GrantedAuthority { } } + public static List getNamesForAllRoles(){ + return List.of( + SEB_SERVER_ADMIN.getName(), + INSTITUTIONAL_ADMIN.getName(), + EXAM_ADMIN.getName(), + EXAM_SUPPORTER.getName()); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/AdminUserInitializer.java b/src/main/java/ch/ethz/seb/sebserver/webservice/AdminUserInitializer.java index f5720243..6398f79e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/AdminUserInitializer.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/AdminUserInitializer.java @@ -8,10 +8,15 @@ package ch.ethz.seb.sebserver.webservice; -import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Map; +import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.Domain; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,26 +45,32 @@ class AdminUserInitializer { private static final Logger log = LoggerFactory.getLogger(AdminUserInitializer.class); + private final WebserviceInfo webserviceInfo; private final UserDAO userDAO; private final InstitutionDAO institutionDAO; private final PasswordEncoder passwordEncoder; + private final AdditionalAttributesDAO additionalAttributesDAO; private final boolean initializeAdmin; private final String adminName; private final String orgName; private final Environment environment; public AdminUserInitializer( + final WebserviceInfo webserviceInfo, final UserDAO userDAO, final InstitutionDAO institutionDAO, + final AdditionalAttributesDAO additionalAttributesDAO, final Environment environment, @Qualifier(WebSecurityConfig.USER_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder passwordEncoder, @Value("${sebserver.init.adminaccount.gen-on-init:false}") final boolean initializeAdmin, @Value("${sebserver.init.adminaccount.username:seb-server-admin}") final String adminName, @Value("${sebserver.init.organisation.name:[SET_ORGANIZATION_NAME]}") final String orgName) { + this.webserviceInfo = webserviceInfo; this.environment = environment; this.userDAO = userDAO; this.institutionDAO = institutionDAO; + this.additionalAttributesDAO = additionalAttributesDAO; this.passwordEncoder = passwordEncoder; this.initializeAdmin = initializeAdmin; this.adminName = adminName; @@ -68,7 +79,7 @@ class AdminUserInitializer { void initAdminAccount() { if (!this.initializeAdmin) { - log.debug("Create initial admin account is switched on off"); + log.debug("Create initial admin account is switched off"); return; } @@ -90,7 +101,7 @@ class AdminUserInitializer { this.userDAO.changePassword( sebServerUser.getUserInfo().getModelId(), generateAdminPassword); - this.writeAdminCredentials(this.adminName, generateAdminPassword); + this.printAdminCredentials(this.adminName, generateAdminPassword); } } } else { @@ -133,10 +144,14 @@ class AdminUserInitializer { null, null, null, - new HashSet<>(Arrays.asList(UserRole.SEB_SERVER_ADMIN.name())))) + new HashSet<>(this.webserviceInfo.isLightSetup() ? + UserRole.getNamesForAllRoles() : + List.of(UserRole.SEB_SERVER_ADMIN.name()) + ))) .flatMap(account -> this.userDAO.setActive(account, true)) .map(account -> { - writeAdminCredentials(this.adminName, generateAdminPassword); + printAdminCredentials(this.adminName, generateAdminPassword); + if(this.webserviceInfo.isLightSetup()) writeInitialAdminCredentialsIntoDB(this.adminName, generateAdminPassword); return account; }) .getOrThrow(); @@ -148,7 +163,7 @@ class AdminUserInitializer { } } - private void writeAdminCredentials(final String name, final CharSequence pwd) { + private void printAdminCredentials(final String name, final CharSequence pwd) { SEBServerInit.INIT_LOGGER.info("---->"); SEBServerInit.INIT_LOGGER.info( "----> ******************************************************************************************" @@ -163,6 +178,23 @@ class AdminUserInitializer { SEBServerInit.INIT_LOGGER.info("---->"); } + private void writeInitialAdminCredentialsIntoDB(final String name, final CharSequence pwd){ + Result.tryCatch(() -> { + final Map attributes = new HashMap<>(); + attributes.put( + Domain.USER.ATTR_USERNAME, + name); + attributes.put( + Domain.USER.ATTR_PASSWORD, + String.valueOf(pwd)); + + this.additionalAttributesDAO.saveAdditionalAttributes( + EntityType.USER, + 2L, + attributes); + }); + } + private CharSequence generateAdminPassword() { try { return ClientCredentialServiceImpl.generateClientSecret(); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/light/impl/LightInit.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/light/impl/LightInit.java index 7f60c9fc..7f66c462 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/light/impl/LightInit.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/light/impl/LightInit.java @@ -22,12 +22,11 @@ public class LightInit { this.sebClientConfigDAO = sebClientConfigDAO; } - - @EventListener(SEBServerInitEvent.class) public void init() { if(isConnectionConfigAbsent()){ - this.sebClientConfigDAO.createNew(createLightConnectionConfiguration()).getOrThrow(); + this.sebClientConfigDAO.createNew(createLightConnectionConfiguration()) + .getOrThrow(); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPIDiscoveryLightController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LightController.java similarity index 66% rename from src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPIDiscoveryLightController.java rename to src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LightController.java index 09aab832..a34b3047 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPIDiscoveryLightController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LightController.java @@ -9,12 +9,16 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api; import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig; +import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SEBClientConfigDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService; +import com.fasterxml.jackson.annotation.JsonCreator; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.http.MediaType; @@ -27,32 +31,34 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; -import java.util.stream.Collectors; @WebServiceProfile @RestController @RequestMapping("${sebserver.webservice.api.exam.endpoint.discovery}") @ConditionalOnExpression("'${sebserver.webservice.light.setup}'.equals('true')") -public class ExamAPIDiscoveryLightController { +public class LightController { private final SEBClientConnectionService sebClientConnectionService; private final SEBClientConfigDAO sebClientConfigDAO; + private final AdditionalAttributesDAO additionalAttributesDAO; private final Executor executor; - protected ExamAPIDiscoveryLightController( + protected LightController( final SEBClientConnectionService sebClientConnectionService, final SEBClientConfigDAO sebClientConfigDAO, + final AdditionalAttributesDAO additionalAttributesDAO, @Qualifier(AsyncServiceSpringConfig.EXAM_API_EXECUTOR_BEAN_NAME) final Executor executor) { this.sebClientConnectionService = sebClientConnectionService; this.sebClientConfigDAO = sebClientConfigDAO; + this.additionalAttributesDAO = additionalAttributesDAO; this.executor = executor; } //this.examAPI_V1_Endpoint + API.EXAM_API_CONFIGURATION_LIGHT_ENDPOINT //http://localhost:8080/exam-api/discovery/light-config @RequestMapping( - path = API.EXAM_API_CONFIGURATION_LIGHT_ENDPOINT, + path = API.EXAM_API_CONFIGURATION_LIGHT_ENDPOINT, method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) public CompletableFuture getLightConfig( @@ -76,6 +82,36 @@ public class ExamAPIDiscoveryLightController { } + @RequestMapping( + path = API.EXAM_API_CONFIGURATION_LIGHT_ENDPOINT + API.PASSWORD_PATH_SEGMENT, + method = RequestMethod.GET, + produces = MediaType.APPLICATION_JSON_VALUE) + public UsernamePasswordView getInitialAdminPassword( + final HttpServletRequest request, + final HttpServletResponse response){ + + try { + final String username = this.additionalAttributesDAO.getAdditionalAttribute(EntityType.USER, 2L, Domain.USER.ATTR_USERNAME) + .getOrThrow() + .getValue(); + + final String password = this.additionalAttributesDAO.getAdditionalAttribute(EntityType.USER, 2L, Domain.USER.ATTR_PASSWORD) + .getOrThrow() + .getValue(); + + return new UsernamePasswordView( + username, + password + ); + + } catch (final Exception e) { + return new UsernamePasswordView( + "initial username missing or changed", + "inital password missing or changed" + ); + } + } + private String getSebClientConfigId() { return this.sebClientConfigDAO .allMatching( @@ -87,9 +123,15 @@ public class ExamAPIDiscoveryLightController { ) .getOrThrow() .stream() - .collect(Collectors.toList()) + .toList() .get(0) .getModelId(); } } + +record UsernamePasswordView(String username, String password) { + @JsonCreator + UsernamePasswordView { + } +} \ No newline at end of file diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserAccountController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserAccountController.java index 3db81134..346f12f0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserAccountController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserAccountController.java @@ -8,18 +8,39 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api; -import java.util.ArrayList; -import java.util.Collection; -import java.util.EnumSet; -import java.util.List; - -import javax.validation.Valid; - +import ch.ethz.seb.sebserver.WebSecurityConfig; +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.api.APIMessage; +import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException; +import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.api.POSTMapper; +import ch.ethz.seb.sebserver.gbl.api.authorization.Privilege; +import ch.ethz.seb.sebserver.gbl.model.Domain; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport; -import ch.ethz.seb.sebserver.gbl.model.user.*; +import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange; +import ch.ethz.seb.sebserver.gbl.model.user.UserAccount; +import ch.ethz.seb.sebserver.gbl.model.user.UserFeatures; +import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; +import ch.ethz.seb.sebserver.gbl.model.user.UserLogActivityType; +import ch.ethz.seb.sebserver.gbl.model.user.UserMod; +import ch.ethz.seb.sebserver.gbl.model.user.UserRole; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Pair; +import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.webservice.WebserviceInfo; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserRecordDynamicSqlSupport; +import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; +import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.FeatureService; +import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser; +import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ScreenProctoringService; +import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService; +import ch.ethz.seb.sebserver.webservice.weblayer.oauth.RevokeTokenEndpoint; import org.mybatis.dynamic.sql.SqlTable; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationEventPublisher; @@ -32,25 +53,11 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; -import ch.ethz.seb.sebserver.WebSecurityConfig; -import ch.ethz.seb.sebserver.gbl.api.API; -import ch.ethz.seb.sebserver.gbl.api.APIMessage; -import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException; -import ch.ethz.seb.sebserver.gbl.api.EntityType; -import ch.ethz.seb.sebserver.gbl.api.POSTMapper; -import ch.ethz.seb.sebserver.gbl.api.authorization.Privilege; -import ch.ethz.seb.sebserver.gbl.model.EntityKey; -import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; -import ch.ethz.seb.sebserver.gbl.util.Result; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserRecordDynamicSqlSupport; -import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; -import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; -import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser; -import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO; -import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService; -import ch.ethz.seb.sebserver.webservice.weblayer.oauth.RevokeTokenEndpoint; +import javax.validation.Valid; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; @WebServiceProfile @RestController @@ -61,6 +68,8 @@ public class UserAccountController extends ActivatableEntityController this.userActivityLogDAO.log(UserLogActivityType.PASSWORD_CHANGE, e)) .map(this::synchronizeUserWithSPS) + .map(userInfo -> { + if(this.webserviceInfo.isLightSetup()){ + return removeInitialAdminPasswordFromDB(userInfo); + } + return userInfo; + }) .getOrThrow(); } @@ -293,4 +312,13 @@ public class UserAccountController extends ActivatableEntityController { + this.additionalAttributesDAO.delete(EntityType.USER, 2L, Domain.USER.ATTR_USERNAME); + this.additionalAttributesDAO.delete(EntityType.USER, 2L, Domain.USER.ATTR_PASSWORD); + }); + + return userInfo; + } }