SEBSLI-9 store admin pw in db + expose endpoint in ssl mode + remove admin pw after pw change

This commit is contained in:
Nadim Ritter 2024-03-20 16:04:00 +01:00
parent 32fc2817d4
commit ed9e86436b
6 changed files with 151 additions and 41 deletions

View file

@ -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);

View file

@ -60,4 +60,12 @@ public enum UserRole implements Entity, GrantedAuthority {
}
}
public static List<String> getNamesForAllRoles(){
return List.of(
SEB_SERVER_ADMIN.getName(),
INSTITUTIONAL_ADMIN.getName(),
EXAM_ADMIN.getName(),
EXAM_SUPPORTER.getName());
}
}

View file

@ -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<String, String> 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();

View file

@ -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();
}
}

View file

@ -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<Void> 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 {
}
}

View file

@ -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<UserInfo,
private final UserDAO userDAO;
private final PasswordEncoder userPasswordEncoder;
private final ScreenProctoringService screenProctoringService;
private final AdditionalAttributesDAO additionalAttributesDAO;
private final WebserviceInfo webserviceInfo;
private final FeatureService featureService;
@ -73,6 +82,8 @@ public class UserAccountController extends ActivatableEntityController<UserInfo,
final ApplicationEventPublisher applicationEventPublisher,
final BeanValidationService beanValidationService,
final ScreenProctoringService screenProctoringService,
final AdditionalAttributesDAO additionalAttributesDAO,
final WebserviceInfo webserviceInfo,
final FeatureService featureService,
@Qualifier(WebSecurityConfig.USER_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder userPasswordEncoder) {
@ -86,6 +97,8 @@ public class UserAccountController extends ActivatableEntityController<UserInfo,
this.userDAO = userDAO;
this.userPasswordEncoder = userPasswordEncoder;
this.screenProctoringService = screenProctoringService;
this.additionalAttributesDAO = additionalAttributesDAO;
this.webserviceInfo = webserviceInfo;
this.featureService = featureService;
}
@ -188,6 +201,12 @@ public class UserAccountController extends ActivatableEntityController<UserInfo,
.flatMap(this::revokeAccessToken)
.flatMap(e -> 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<UserInfo,
screenProctoringService.synchronizeSPSUser(userInfo.uuid);
return userInfo;
}
private UserInfo removeInitialAdminPasswordFromDB(final UserInfo userInfo){
Result.tryCatch(() -> {
this.additionalAttributesDAO.delete(EntityType.USER, 2L, Domain.USER.ATTR_USERNAME);
this.additionalAttributesDAO.delete(EntityType.USER, 2L, Domain.USER.ATTR_PASSWORD);
});
return userInfo;
}
}