diff --git a/.github/workflows/buildReporting.yml b/.github/workflows/buildReporting.yml index 73222563..28aa6e18 100644 --- a/.github/workflows/buildReporting.yml +++ b/.github/workflows/buildReporting.yml @@ -94,7 +94,7 @@ jobs: uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@v3.2.0 - name: Login to DockerHub uses: docker/login-action@v2 diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java index d5299935..de9dc9a8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java @@ -102,6 +102,8 @@ public final class Constants { public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd"; public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss"; + public static final Long LIGHT_ADMIN_USER_ID = 1L; + public static final DateTimeFormatter STANDARD_DATE_TIME_MILLIS_FORMATTER = DateTimeFormat .forPattern(DEFAULT_DATE_TIME_MILLIS_FORMAT) .withZoneUTC(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index a18ecdb9..71a32884 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -126,6 +126,8 @@ public final class API { public static final String EXAM_API_CONFIGURATION_REQUEST_ENDPOINT = "/examconfig"; + public static final String EXAM_API_CONFIGURATION_LIGHT_ENDPOINT = "/light-config"; + public static final String EXAM_API_PING_ENDPOINT = "/sebping"; public static final String EXAM_API_PING_TIMESTAMP = "timestamp"; 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..d1168083 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 @@ -12,6 +12,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.List; +import java.util.stream.Collectors; import org.springframework.security.core.GrantedAuthority; @@ -60,4 +61,10 @@ public enum UserRole implements Entity, GrantedAuthority { } } + public static List getNamesForAllRoles(){ + return Arrays.stream(UserRole.values()) + .map(UserRole::getName) + .toList(); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/GuiWebsecurityConfig.java b/src/main/java/ch/ethz/seb/sebserver/gui/GuiWebsecurityConfig.java index 8ff2351e..7b36682c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/GuiWebsecurityConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/GuiWebsecurityConfig.java @@ -86,6 +86,8 @@ public class GuiWebsecurityConfig extends WebSecurityConfigurerAdapter { .antMatchers(API.ERROR_PATH).permitAll() .antMatchers(API.CHECK_PATH).permitAll() .antMatchers(this.examAPIDiscoveryEndpoint).permitAll() + .antMatchers(this.examAPIDiscoveryEndpoint + API.EXAM_API_CONFIGURATION_LIGHT_ENDPOINT).permitAll() + .antMatchers(this.examAPIDiscoveryEndpoint + API.EXAM_API_CONFIGURATION_LIGHT_ENDPOINT + API.PASSWORD_PATH_SEGMENT).permitAll() .antMatchers(adminAPIEndpoint + API.INFO_ENDPOINT + API.LOGO_PATH_SEGMENT + "/**").permitAll() .antMatchers(adminAPIEndpoint + API.INFO_ENDPOINT + API.INFO_INST_PATH_SEGMENT + "/**").permitAll() .antMatchers(adminAPIEndpoint + API.REGISTER_ENDPOINT).permitAll() 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..aa164f60 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,16 @@ 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.Constants; +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 +46,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 +80,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 +102,7 @@ class AdminUserInitializer { this.userDAO.changePassword( sebServerUser.getUserInfo().getModelId(), generateAdminPassword); - this.writeAdminCredentials(this.adminName, generateAdminPassword); + this.printAdminCredentials(this.adminName, generateAdminPassword); } } } else { @@ -133,10 +145,16 @@ 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 +166,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 +181,23 @@ class AdminUserInitializer { SEBServerInit.INIT_LOGGER.info("---->"); } + private void writeInitialAdminCredentialsIntoDB(final String name, final CharSequence pwd){ + try { + 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, Constants.LIGHT_ADMIN_USER_ID, attributes); + + } catch (final Exception e) { + log.error("Unable to write initial admin credentials into the additional attributes table: ", e); + } + } + private CharSequence generateAdminPassword() { try { return ClientCredentialServiceImpl.generateClientSecret(); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java index 962947b7..e12b5bff 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceInit.java @@ -102,14 +102,16 @@ public class WebserviceInit implements ApplicationListener Initialize Services..."); SEBServerInit.INIT_LOGGER.info("----> "); - this.applicationEventPublisher.publishEvent(new SEBServerInitEvent(this)); - // Run the database integrity checks and fixes if configured this.dbIntegrityChecker.checkIntegrity(); // Create an initial admin account if requested and not already in the database this.adminUserInitializer.initAdminAccount(); + //emits SEBServerInitEvent + this.applicationEventPublisher.publishEvent(new SEBServerInitEvent(this)); + + SEBServerInit.INIT_LOGGER.info("----> *********************************************************"); SEBServerInit.INIT_LOGGER.info("----> *** Webservice Info: ***"); SEBServerInit.INIT_LOGGER.info("----> *********************************************************"); 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 new file mode 100644 index 00000000..7f66c462 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/light/impl/LightInit.java @@ -0,0 +1,78 @@ +package ch.ethz.seb.sebserver.webservice.servicelayer.light.impl; + + +import ch.ethz.seb.sebserver.SEBServerInitEvent; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SEBClientConfigDAO; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; + +import java.util.Collection; + +@Lazy +@Service +@ConditionalOnExpression("'${sebserver.webservice.light.setup}'.equals('true')") +public class LightInit { + + private final SEBClientConfigDAO sebClientConfigDAO; + + public LightInit(final SEBClientConfigDAO sebClientConfigDAO){ + this.sebClientConfigDAO = sebClientConfigDAO; + } + + @EventListener(SEBServerInitEvent.class) + public void init() { + if(isConnectionConfigAbsent()){ + this.sebClientConfigDAO.createNew(createLightConnectionConfiguration()) + .getOrThrow(); + } + } + + private boolean isConnectionConfigAbsent() { + Collection connectionConfigs = this.sebClientConfigDAO + .all(null, null) + .getOrThrow(); + + if(connectionConfigs.size() == 0){ + return true; + } + + return false; + } + + private SEBClientConfig createLightConnectionConfiguration(){ + return new SEBClientConfig( + 1L, + 1L, + "light-config", + SEBClientConfig.ConfigPurpose.CONFIGURE_CLIENT, + 1000L, + SEBClientConfig.VDIType.NO, + null, + null, + null, + false, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + false, + true, + null, + null, + null); + } + + + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientConnectionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientConnectionService.java index 08d3b270..d0825931 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientConnectionService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientConnectionService.java @@ -9,6 +9,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; import java.security.Principal; import java.util.Collection; @@ -173,4 +174,8 @@ public interface SEBClientConnectionService { String ipAddress, HttpServletResponse response); + void streamLightExamConfig( + String modelId, + HttpServletResponse response) throws IOException; + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java index 2720572d..83328640 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java @@ -8,23 +8,36 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServletResponse; -import java.security.Principal; -import java.util.*; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; - +import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.model.Entity; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; +import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; import ch.ethz.seb.sebserver.gbl.model.exam.ScreenProctoringSettings; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig.VDIType; +import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; +import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; +import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.webservice.WebserviceInfo; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SEBClientConfigDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService; +import ch.ethz.seb.sebserver.webservice.servicelayer.institution.SecurityKeyService; +import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.DistributedIndicatorValueService; +import ch.ethz.seb.sebserver.webservice.weblayer.api.APIConstraintViolationException; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -34,25 +47,20 @@ import org.springframework.http.HttpStatus; import org.springframework.security.access.AccessDeniedException; import org.springframework.stereotype.Service; -import ch.ethz.seb.sebserver.gbl.Constants; -import ch.ethz.seb.sebserver.gbl.model.EntityKey; -import ch.ethz.seb.sebserver.gbl.model.exam.Exam; -import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig; -import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig.VDIType; -import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; -import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; -import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; -import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; -import ch.ethz.seb.sebserver.gbl.util.Result; -import ch.ethz.seb.sebserver.webservice.WebserviceInfo; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SEBClientConfigDAO; -import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService; -import ch.ethz.seb.sebserver.webservice.servicelayer.institution.SecurityKeyService; -import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; -import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService; -import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.DistributedIndicatorValueService; -import ch.ethz.seb.sebserver.webservice.weblayer.api.APIConstraintViolationException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.security.Principal; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; +import java.util.UUID; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; @Lazy @Service @@ -79,6 +87,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic private final SecurityKeyService securityKeyService; private final SEBClientEventBatchService sebClientEventBatchService; private final SEBClientInstructionService sebClientInstructionService; + private final ClientConfigService clientConfigService; private final JSONMapper jsonMapper; private final boolean isDistributedSetup; @@ -92,6 +101,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic final WebserviceInfo webserviceInfo, final SEBClientEventBatchService sebClientEventBatchService, final SEBClientInstructionService sebClientInstructionService, + final ClientConfigService clientConfigService, final JSONMapper jsonMapper) { this.examSessionService = examSessionService; @@ -105,6 +115,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic this.isDistributedSetup = webserviceInfo.isDistributed(); this.sebClientEventBatchService = sebClientEventBatchService; this.sebClientInstructionService = sebClientInstructionService; + this.clientConfigService = clientConfigService; this.jsonMapper = jsonMapper; } @@ -623,6 +634,42 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic } } + public void streamLightExamConfig(final String modelId, final HttpServletResponse response) throws IOException{ + + final ServletOutputStream outputStream = response.getOutputStream(); + PipedOutputStream pout; + PipedInputStream pin; + + try { + pout = new PipedOutputStream(); + pin = new PipedInputStream(pout); + + this.clientConfigService.exportSEBClientConfiguration( + pout, + modelId, + null); + + IOUtils.copyLarge(pin, outputStream); + + response.setStatus(HttpStatus.OK.value()); + + }catch(Exception e){ + final APIMessage errorMessage = APIMessage.ErrorMessage.GENERIC.of(e.getMessage()); + outputStream.write(Utils.toByteArray(this.jsonMapper.writeValueAsString(errorMessage))); + response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); + + } finally { + + try { + outputStream.flush(); + outputStream.close(); + + } catch (IOException e) { + log.error("error while flushing / closing output stream", e); + } + } + } + private void writeSEBClientErrors( final HttpServletResponse response, final Collection errorMessages) { @@ -938,4 +985,4 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic return (exam != null && exam.lmsSetupId == null && status == ConnectionStatus.READY) || status == ConnectionStatus.ACTIVE; } -} +} \ No newline at end of file diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LightController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LightController.java new file mode 100644 index 00000000..96bf02e5 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LightController.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2024 ETH Zürich, IT Services / Informatikdienste (ID) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.webservice.weblayer.api; + +import ch.ethz.seb.sebserver.gbl.Constants; +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; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +@WebServiceProfile +@RestController +@RequestMapping("${sebserver.webservice.api.exam.endpoint.discovery}") +@ConditionalOnExpression("'${sebserver.webservice.light.setup}'.equals('true')") +public class LightController { + + private final SEBClientConnectionService sebClientConnectionService; + private final SEBClientConfigDAO sebClientConfigDAO; + private final AdditionalAttributesDAO additionalAttributesDAO; + private final Executor executor; + + 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; + } + + @RequestMapping( + path = API.EXAM_API_CONFIGURATION_LIGHT_ENDPOINT, + method = RequestMethod.GET, + produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + public CompletableFuture getLightConfig( + final HttpServletRequest request, + final HttpServletResponse response){ + + return CompletableFuture.runAsync( + () -> { + try { + this.sebClientConnectionService.streamLightExamConfig("1", response); + } catch (IOException e) { + throw new RuntimeException(e); + } + }, + this.executor + ); + + } + + @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, Constants.LIGHT_ADMIN_USER_ID, Domain.USER.ATTR_USERNAME) + .getOrThrow() + .getValue(); + + final String password = this.additionalAttributesDAO.getAdditionalAttribute(EntityType.USER, Constants.LIGHT_ADMIN_USER_ID, 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( + new FilterMap().putIfAbsent( + "active", + String.valueOf(true) + ), + Utils.truePredicate() + ) + .getOrThrow() + .stream() + .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..4b08afc7 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,19 +8,43 @@ 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.Constants; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.MediaType; @@ -32,35 +56,25 @@ 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 @RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.USER_ACCOUNT_ENDPOINT) public class UserAccountController extends ActivatableEntityController { + private static final Logger log = LoggerFactory.getLogger(UserAccountController.class); + private final ApplicationEventPublisher applicationEventPublisher; 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 +87,8 @@ public class UserAccountController extends ActivatableEntityController this.userActivityLogDAO.log(UserLogActivityType.PASSWORD_CHANGE, e)) .map(this::synchronizeUserWithSPS) + .map(this::removeInitialAdminPasswordFromDB) .getOrThrow(); } @@ -293,4 +312,21 @@ public class UserAccountController extends ActivatableEntityController