merged development

This commit is contained in:
anhefti 2024-03-21 10:13:08 +01:00
commit a1660de341
10 changed files with 69 additions and 37 deletions

View file

@ -53,6 +53,8 @@ jobs:
- -
name: Reporting name: Reporting
uses: codecov/codecov-action@v4 uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with: with:
flags: unittests flags: unittests
name: SEB Server Build name: SEB Server Build
@ -92,7 +94,7 @@ jobs:
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
- -
name: Set up Docker Buildx name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v4
- -
name: Login to DockerHub name: Login to DockerHub
uses: docker/login-action@v2 uses: docker/login-action@v2
@ -147,13 +149,13 @@ jobs:
TAGS: ${{ steps.meta.outputs.tags }} TAGS: ${{ steps.meta.outputs.tags }}
cleanup: cleanup:
needs: docker-build needs: [maven-build-reporting, docker-build]
# Run only on tagging if: |
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') always()
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- -
name: Delete Artifacts name: Delete Artifacts
uses: geekyeggo/delete-artifact@v4 uses: geekyeggo/delete-artifact@v5
with: with:
name: Package name: Package

View file

@ -40,9 +40,6 @@ import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
@Order(7) @Order(7)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements ErrorController { public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements ErrorController {
private static final String ERROR_PATH = "/sebserver/error";
private static final String CHECK_PATH = "/sebserver/check";
@Value("${sebserver.webservice.http.redirect.gui}") @Value("${sebserver.webservice.http.redirect.gui}")
private String guiRedirect; private String guiRedirect;
@Value("${sebserver.webservice.api.exam.endpoint.discovery}") @Value("${sebserver.webservice.api.exam.endpoint.discovery}")
@ -77,25 +74,11 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements E
return new BCryptPasswordEncoder(4); return new BCryptPasswordEncoder(4);
} }
@Override @RequestMapping(API.CHECK_PATH)
public void configure(final WebSecurity web) {
web
.ignoring()
.antMatchers(ERROR_PATH)
.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);
}
@RequestMapping(CHECK_PATH)
public void check() throws IOException { public void check() throws IOException {
} }
@RequestMapping(ERROR_PATH) @RequestMapping(API.ERROR_PATH)
public void handleError(final HttpServletResponse response) throws IOException { public void handleError(final HttpServletResponse response) throws IOException {
response.getOutputStream().print(response.getStatus()); response.getOutputStream().print(response.getStatus());
response.setHeader(HttpHeaders.LOCATION, this.guiRedirect); response.setHeader(HttpHeaders.LOCATION, this.guiRedirect);

View file

@ -12,6 +12,9 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
public final class API { public final class API {
public static final String ERROR_PATH = "/sebserver/error";
public static final String CHECK_PATH = "/sebserver/check";
public enum BulkActionType { public enum BulkActionType {
HARD_DELETE, HARD_DELETE,
DEACTIVATE, DEACTIVATE,

View file

@ -281,7 +281,7 @@ public final class Exam implements GrantEntity {
} }
return result; return result;
} else { } else {
return null; return Collections.emptyList();
} }
} }

View file

@ -42,6 +42,10 @@ public class GuiWebsecurityConfig extends WebSecurityConfigurerAdapter {
private String remoteProctoringViewServletEndpoint; private String remoteProctoringViewServletEndpoint;
@Value("${springdoc.api-docs.enabled:false}") @Value("${springdoc.api-docs.enabled:false}")
private boolean springDocsAPIEnabled; private boolean springDocsAPIEnabled;
@Value("${sebserver.webservice.api.exam.endpoint.discovery}")
private String examAPIDiscoveryEndpoint;
@Value("${sebserver.webservice.api.admin.endpoint}")
private String adminAPIEndpoint;
/** Gui-service related public URLS from spring web security perspective */ /** Gui-service related public URLS from spring web security perspective */
public static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher( public static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher(
@ -52,28 +56,42 @@ public class GuiWebsecurityConfig extends WebSecurityConfigurerAdapter {
// project specific static resources // project specific static resources
new AntPathRequestMatcher("/images/**"), new AntPathRequestMatcher("/images/**"),
new AntPathRequestMatcher("/favicon.ico")); new AntPathRequestMatcher("/favicon.ico")
);
@Override @Override
public void configure(final WebSecurity web) { public void configure(final WebSecurity web) {
web web
.ignoring() .ignoring()
.requestMatchers(PUBLIC_URLS) .antMatchers(this.guiEntryPoint)
.antMatchers(this.guiEntryPoint) ;
.antMatchers(this.remoteProctoringEndpoint)
.antMatchers(this.remoteProctoringEndpoint + this.remoteProctoringViewServletEndpoint + "/*");
if (this.springDocsAPIEnabled) { if (this.springDocsAPIEnabled) {
web.ignoring().antMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**"); web.ignoring().antMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**");
} }
} }
@Override @Override
public void configure(final HttpSecurity http) throws Exception { public void configure(final HttpSecurity http) throws Exception {
http http
.sessionManagement() .sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and() .and()
.authorizeRequests()
.antMatchers(this.remoteProctoringEndpoint).permitAll()
.antMatchers(this.remoteProctoringEndpoint + this.remoteProctoringViewServletEndpoint + "/*").permitAll()
.requestMatchers(PUBLIC_URLS).permitAll()
.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()
.and()
.antMatcher("/**") .antMatcher("/**")
.authorizeRequests() .authorizeRequests()
.anyRequest() .anyRequest()

View file

@ -57,6 +57,8 @@ public class ExamIndicatorsList implements TemplateComposer {
new LocTextKey("sebserver.exam.indicator.list.pleaseSelect"); new LocTextKey("sebserver.exam.indicator.list.pleaseSelect");
private static final LocTextKey INDICATOR_EMPTY_LIST_MESSAGE = private static final LocTextKey INDICATOR_EMPTY_LIST_MESSAGE =
new LocTextKey("sebserver.exam.indicator.list.empty"); new LocTextKey("sebserver.exam.indicator.list.empty");
private static final LocTextKey CONFIRM_MESSAGE_REMOVE_INDICATOR =
new LocTextKey("sebserver.exam.indicator.list.delete.confirm");
private final PageService pageService; private final PageService pageService;
private final ResourceService resourceService; private final ResourceService resourceService;
@ -144,6 +146,7 @@ public class ExamIndicatorsList implements TemplateComposer {
indicatorTable::getMultiSelection, indicatorTable::getMultiSelection,
this::deleteSelectedIndicator, this::deleteSelectedIndicator,
INDICATOR_EMPTY_SELECTION_TEXT_KEY) INDICATOR_EMPTY_SELECTION_TEXT_KEY)
.withConfirm(() -> CONFIRM_MESSAGE_REMOVE_INDICATOR)
.publishIf(() -> indicatorEnabled && !isLight && editable && indicatorTable.hasAnyContent(), false) .publishIf(() -> indicatorEnabled && !isLight && editable && indicatorTable.hasAnyContent(), false)
.newAction(ActionDefinition.EXAM_INDICATOR_NEW) .newAction(ActionDefinition.EXAM_INDICATOR_NEW)

View file

@ -32,11 +32,12 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.AttributeValueCon
@WebServiceProfile @WebServiceProfile
public class RealNumberConverter implements AttributeValueConverter { public class RealNumberConverter implements AttributeValueConverter {
private static final Logger log = LoggerFactory.getLogger(IntegerConverter.class); private static final Logger log = LoggerFactory.getLogger(RealNumberConverter.class);
public static final Set<String> SUPPORTED_ATTR_NAMES = Utils.immutableSetOf( public static final Set<String> SUPPORTED_ATTR_NAMES = Utils.immutableSetOf(
"defaultPageZoomLevel", "defaultPageZoomLevel",
"defaultTextZoomLevel"); "defaultTextZoomLevel",
"screenProctoringImageDownscale");
private static final String XML_TEMPLATE = "<key>%s</key><real>%s</real>"; private static final String XML_TEMPLATE = "<key>%s</key><real>%s</real>";
private static final String JSON_TEMPLATE = "\"%s\":%s"; private static final String JSON_TEMPLATE = "\"%s\":%s";
@ -89,6 +90,12 @@ public class RealNumberConverter implements AttributeValueConverter {
realVal = 0; realVal = 0;
} }
// NOTE: this is a special case for screenProctoringImageDownscale selector to get the selected real value
// from the selection-index instead using the index. See SEBSERV-527
if ("screenProctoringImageDownscale".equals(attribute.name)) {
realVal = realVal / 10.0 + 1.0;
}
out.write(Utils.toByteArray(String.format( out.write(Utils.toByteArray(String.format(
template, template,
AttributeValueConverter.extractName(attribute), AttributeValueConverter.extractName(attribute),

View file

@ -13,6 +13,7 @@ import java.io.IOException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import ch.ethz.seb.sebserver.gbl.api.API;
import org.apache.catalina.filters.RemoteIpFilter; import org.apache.catalina.filters.RemoteIpFilter;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -50,18 +51,18 @@ import ch.ethz.seb.sebserver.webservice.weblayer.oauth.WebClientDetailsService;
import ch.ethz.seb.sebserver.webservice.weblayer.oauth.WebserviceResourceConfiguration; import ch.ethz.seb.sebserver.webservice.weblayer.oauth.WebserviceResourceConfiguration;
/** This is the main web-security Spring configuration for SEB-Server webservice API /** This is the main web-security Spring configuration for SEB-Server webservice API
* * <p>
* Currently two separated Rest API's are implemented, one for administration and maintenance * Currently two separated Rest API's are implemented, one for administration and maintenance
* of the SEB-Server (AdminAPI) and one for SEB-Client connection on running exams and eventually * of the SEB-Server (AdminAPI) and one for SEB-Client connection on running exams and eventually
* also for LMS communication), if needed (ExamAPI). The AdministrationAPI uses OAuth 2 password * also for LMS communication), if needed (ExamAPI). The AdministrationAPI uses OAuth 2 password
* grant with refresh-token, same as in the prototype and the ExamAPI uses the client_credential grant. * grant with refresh-token, same as in the prototype and the ExamAPI uses the client_credential grant.
* * <p>
* There is a Spring Authorization-Server defining this two clients (AdminAPIClient and ExamAPIClient) as well as * There is a Spring Authorization-Server defining this two clients (AdminAPIClient and ExamAPIClient) as well as
* two Spring Resource-Server for the separation of the different API's * two Spring Resource-Server for the separation of the different API's
* * <p>
* The endpoint of the AdministrationAPI can be configured within the key; sebserver.webservice.api.admin.endpoint * The endpoint of the AdministrationAPI can be configured within the key; sebserver.webservice.api.admin.endpoint
* and is by default set to "/admin-api/**" * and is by default set to "/admin-api/**"
* * <p>
* The endpoint of the ExamAPI can be configured within the key; sebserver.webservice.api.exam.endpoint * The endpoint of the ExamAPI can be configured within the key; sebserver.webservice.api.exam.endpoint
* and is by default set to "/exam-api/**" */ * and is by default set to "/exam-api/**" */
@WebServiceProfile @WebServiceProfile

View file

@ -11,6 +11,8 @@ package ch.ethz.seb.sebserver.webservice.weblayer.oauth;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import ch.ethz.seb.sebserver.gbl.api.API;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.http.SessionCreationPolicy;
@ -30,6 +32,8 @@ public abstract class WebserviceResourceConfiguration extends ResourceServerConf
public static final String ADMIN_API_RESOURCE_ID = "seb-server-administration-api"; public static final String ADMIN_API_RESOURCE_ID = "seb-server-administration-api";
/** The resource identifier of the Exam API resources */ /** The resource identifier of the Exam API resources */
public static final String EXAM_API_RESOURCE_ID = "seb-server-exam-api"; public static final String EXAM_API_RESOURCE_ID = "seb-server-exam-api";
@Value("${sebserver.webservice.api.exam.endpoint.discovery}")
private String examAPIDiscoveryEndpoint;
public WebserviceResourceConfiguration( public WebserviceResourceConfiguration(
final TokenStore tokenStore, final TokenStore tokenStore,
@ -74,6 +78,16 @@ public abstract class WebserviceResourceConfiguration extends ResourceServerConf
.sessionManagement() .sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and() .and()
.authorizeRequests()
.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(configurerAdapter.apiEndpoint + API.INFO_ENDPOINT + API.LOGO_PATH_SEGMENT + "/**").permitAll()
.antMatchers(configurerAdapter.apiEndpoint + API.INFO_ENDPOINT + API.INFO_INST_PATH_SEGMENT + "/**").permitAll()
.antMatchers(configurerAdapter.apiEndpoint + API.REGISTER_ENDPOINT).permitAll()
.and()
.antMatcher(configurerAdapter.apiEndpoint + "/**") .antMatcher(configurerAdapter.apiEndpoint + "/**")
.authorizeRequests() .authorizeRequests()
.anyRequest() .anyRequest()

View file

@ -709,6 +709,7 @@ sebserver.exam.indicator.list.column.thresholds=Thresholds
sebserver.exam.indicator.list.column.thresholds.tooltip=The thresholds of the indicator sebserver.exam.indicator.list.column.thresholds.tooltip=The thresholds of the indicator
sebserver.exam.indicator.list.empty=There is currently no indicator defined for this exam. Please create a new one sebserver.exam.indicator.list.empty=There is currently no indicator defined for this exam. Please create a new one
sebserver.exam.indicator.list.pleaseSelect=At first please select an indicator from the list sebserver.exam.indicator.list.pleaseSelect=At first please select an indicator from the list
sebserver.exam.indicator.list.delete.confirm=Are you sure to delete the selected Indicator from the Exam?
sebserver.exam.clientgroup.list.actions=&nbsp; sebserver.exam.clientgroup.list.actions=&nbsp;
sebserver.exam.clientgroup.list.title=Client Groups sebserver.exam.clientgroup.list.title=Client Groups