Merge remote-tracking branch 'origin/dev-1.1.0' into development
Conflicts: src/main/java/ch/ethz/seb/sebserver/gui/form/FormHandle.java
This commit is contained in:
		
						commit
						bbcbc318a9
					
				
					 11 changed files with 196 additions and 10 deletions
				
			
		
							
								
								
									
										5
									
								
								pom.xml
									
										
									
									
									
								
							
							
						
						
									
										5
									
								
								pom.xml
									
										
									
									
									
								
							|  | @ -346,6 +346,11 @@ | ||||||
|       <artifactId>commons-text</artifactId> |       <artifactId>commons-text</artifactId> | ||||||
|       <version>1.8</version> |       <version>1.8</version> | ||||||
|     </dependency> |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>com.github.vladimir-bukhtoyarov</groupId> | ||||||
|  |       <artifactId>bucket4j-core</artifactId> | ||||||
|  |       <version>4.10.0</version> | ||||||
|  |     </dependency> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     <!-- Testing --> |     <!-- Testing --> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,13 @@ | ||||||
|  | /* | ||||||
|  |  * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET) | ||||||
|  |  *  | ||||||
|  |  * 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.gbl.api; | ||||||
|  | 
 | ||||||
|  | public class TooManyRegistrations { | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,32 @@ | ||||||
|  | /* | ||||||
|  |  * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET) | ||||||
|  |  * | ||||||
|  |  * 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.gbl.api; | ||||||
|  | 
 | ||||||
|  | public class TooManyRequests extends RuntimeException { | ||||||
|  | 
 | ||||||
|  |     private static final long serialVersionUID = 3303246002774619224L; | ||||||
|  | 
 | ||||||
|  |     public static enum Code { | ||||||
|  |         INCOMMING, | ||||||
|  |         REGISTRATION | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public final Code code; | ||||||
|  | 
 | ||||||
|  |     public TooManyRequests() { | ||||||
|  |         super("TooManyRequests"); | ||||||
|  |         this.code = Code.INCOMMING; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public TooManyRequests(final Code code) { | ||||||
|  |         super("TooManyRequests"); | ||||||
|  |         this.code = code; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -232,7 +232,7 @@ public class RegisterPage implements TemplateComposer { | ||||||
|         registerButton.addListener(SWT.Selection, event -> { |         registerButton.addListener(SWT.Selection, event -> { | ||||||
| 
 | 
 | ||||||
|             registerForm.getForm().clearErrors(); |             registerForm.getForm().clearErrors(); | ||||||
|             final Result<UserInfo> onError = this.pageService |             final Result<UserInfo> result = this.pageService | ||||||
|                     .getRestService() |                     .getRestService() | ||||||
|                     .getBuilder(RegisterNewUser.class) |                     .getBuilder(RegisterNewUser.class) | ||||||
|                     .withRestTemplate(this.restTemplate) |                     .withRestTemplate(this.restTemplate) | ||||||
|  | @ -240,7 +240,7 @@ public class RegisterPage implements TemplateComposer { | ||||||
|                     .call() |                     .call() | ||||||
|                     .onError(registerForm::handleError); |                     .onError(registerForm::handleError); | ||||||
| 
 | 
 | ||||||
|             if (onError.hasError()) { |             if (result.hasError()) { | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -14,10 +14,13 @@ import java.util.function.Predicate; | ||||||
| import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||||
| 
 | 
 | ||||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import ch.ethz.seb.sebserver.gbl.api.API; | import ch.ethz.seb.sebserver.gbl.api.API; | ||||||
| import ch.ethz.seb.sebserver.gbl.api.APIMessage; | import ch.ethz.seb.sebserver.gbl.api.APIMessage; | ||||||
| import ch.ethz.seb.sebserver.gbl.api.EntityType; | import ch.ethz.seb.sebserver.gbl.api.EntityType; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.api.TooManyRequests; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.Entity; | import ch.ethz.seb.sebserver.gbl.model.Entity; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Result; | import ch.ethz.seb.sebserver.gbl.util.Result; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Utils; | import ch.ethz.seb.sebserver.gbl.util.Utils; | ||||||
|  | @ -35,7 +38,11 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError; | ||||||
| 
 | 
 | ||||||
| public class FormHandle<T extends Entity> { | public class FormHandle<T extends Entity> { | ||||||
| 
 | 
 | ||||||
|  |     private static final Logger log = LoggerFactory.getLogger(FormHandle.class); | ||||||
|  | 
 | ||||||
|     public static final String FIELD_VALIDATION_LOCTEXT_PREFIX = "sebserver.form.validation.fieldError."; |     public static final String FIELD_VALIDATION_LOCTEXT_PREFIX = "sebserver.form.validation.fieldError."; | ||||||
|  |     static final LocTextKey MESSAGE_TOO_MANY_REQUESTS_TEXT = | ||||||
|  |             new LocTextKey("sebserver.error.tooManyRequests"); | ||||||
| 
 | 
 | ||||||
|     private final PageService pageService; |     private final PageService pageService; | ||||||
|     private final PageContext pageContext; |     private final PageContext pageContext; | ||||||
|  | @ -169,12 +176,27 @@ public class FormHandle<T extends Entity> { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void handleUnexpectedError(final Exception error) { |     private void handleUnexpectedError(final Exception error) { | ||||||
|         if (this.post != null && this.post.getEntityType() != null) { |         try { | ||||||
|             this.pageContext.notifySaveError(this.post.getEntityType(), error); |             if (error instanceof TooManyRequests) { | ||||||
|         } else { |                 final TooManyRequests.Code code = ((TooManyRequests) error).code; | ||||||
|             this.pageContext.notifyError( |                 if (code != null) { | ||||||
|                     new LocTextKey(PageContext.GENERIC_SAVE_ERROR_TEXT_KEY, StringUtils.EMPTY), |                     this.pageContext.publishInfo(new LocTextKey(MESSAGE_TOO_MANY_REQUESTS_TEXT.name + "." + code)); | ||||||
|                     error); |                 } else { | ||||||
|  |                     this.pageContext.publishInfo(MESSAGE_TOO_MANY_REQUESTS_TEXT); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (this.post != null && this.post.getEntityType() != null) { | ||||||
|  |                 this.pageContext.notifySaveError(this.post.getEntityType(), error); | ||||||
|  |             } else { | ||||||
|  |                 this.pageContext.notifyError( | ||||||
|  |                         new LocTextKey(PageContext.GENERIC_SAVE_ERROR_TEXT_KEY, StringUtils.EMPTY), | ||||||
|  |                         error); | ||||||
|  |             } | ||||||
|  |         } catch (final Exception e) { | ||||||
|  |             log.error("Failed to handle unexpected error: ", e); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -40,6 +40,7 @@ import ch.ethz.seb.sebserver.gbl.Constants; | ||||||
| import ch.ethz.seb.sebserver.gbl.api.APIMessage; | import ch.ethz.seb.sebserver.gbl.api.APIMessage; | ||||||
| import ch.ethz.seb.sebserver.gbl.api.EntityType; | import ch.ethz.seb.sebserver.gbl.api.EntityType; | ||||||
| import ch.ethz.seb.sebserver.gbl.api.JSONMapper; | import ch.ethz.seb.sebserver.gbl.api.JSONMapper; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.api.TooManyRequests; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.Entity; | import ch.ethz.seb.sebserver.gbl.model.Entity; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.Page; | import ch.ethz.seb.sebserver.gbl.model.Page; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.PageSortOrder; | import ch.ethz.seb.sebserver.gbl.model.PageSortOrder; | ||||||
|  | @ -134,6 +135,15 @@ public abstract class RestCall<T> { | ||||||
|             } |             } | ||||||
|         } catch (final RestClientResponseException responseError) { |         } catch (final RestClientResponseException responseError) { | ||||||
| 
 | 
 | ||||||
|  |             if (responseError.getRawStatusCode() == HttpStatus.TOO_MANY_REQUESTS.value()) { | ||||||
|  |                 final String code = responseError.getResponseBodyAsString(); | ||||||
|  |                 if (StringUtils.isNotBlank(code)) { | ||||||
|  |                     return Result.ofError(new TooManyRequests(TooManyRequests.Code.valueOf(code))); | ||||||
|  |                 } else { | ||||||
|  |                     return Result.ofError(new TooManyRequests()); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             final RestCallError restCallError = new RestCallError("Unexpected error while rest call", responseError); |             final RestCallError restCallError = new RestCallError("Unexpected error while rest call", responseError); | ||||||
|             try { |             try { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -33,6 +33,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExcep | ||||||
| import ch.ethz.seb.sebserver.gbl.api.APIMessage; | import ch.ethz.seb.sebserver.gbl.api.APIMessage; | ||||||
| import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException; | import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException; | ||||||
| import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException; | import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.api.TooManyRequests; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Utils; | import ch.ethz.seb.sebserver.gbl.util.Utils; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PermissionDeniedException; | import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PermissionDeniedException; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException; | import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException; | ||||||
|  | @ -87,6 +88,15 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler { | ||||||
|                 HttpStatus.BAD_REQUEST); |                 HttpStatus.BAD_REQUEST); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @ExceptionHandler(TooManyRequests.class) | ||||||
|  |     public ResponseEntity<Object> handleToManyRequests( | ||||||
|  |             final TooManyRequests ex, | ||||||
|  |             final WebRequest request) { | ||||||
|  |         return ResponseEntity | ||||||
|  |                 .status(HttpStatus.TOO_MANY_REQUESTS) | ||||||
|  |                 .body(String.valueOf(ex.code)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @ExceptionHandler(RuntimeException.class) |     @ExceptionHandler(RuntimeException.class) | ||||||
|     public ResponseEntity<Object> handleRuntimeException( |     public ResponseEntity<Object> handleRuntimeException( | ||||||
|             final RuntimeException ex, |             final RuntimeException ex, | ||||||
|  |  | ||||||
|  | @ -0,0 +1,70 @@ | ||||||
|  | /* | ||||||
|  |  * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET) | ||||||
|  |  * | ||||||
|  |  * 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 java.time.Duration; | ||||||
|  | 
 | ||||||
|  | import org.springframework.context.annotation.Lazy; | ||||||
|  | import org.springframework.core.env.Environment; | ||||||
|  | import org.springframework.stereotype.Service; | ||||||
|  | 
 | ||||||
|  | import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | ||||||
|  | import io.github.bucket4j.Bandwidth; | ||||||
|  | import io.github.bucket4j.Bucket4j; | ||||||
|  | import io.github.bucket4j.Refill; | ||||||
|  | import io.github.bucket4j.local.LocalBucket; | ||||||
|  | 
 | ||||||
|  | @Lazy | ||||||
|  | @Service | ||||||
|  | @WebServiceProfile | ||||||
|  | public class RateLimitService { | ||||||
|  | 
 | ||||||
|  |     private final int requestLimit; | ||||||
|  |     private final int requestLimitInterval; | ||||||
|  |     private final int requestLimitRefill; | ||||||
|  | 
 | ||||||
|  |     private final int createLimit; | ||||||
|  |     private final int createLimitInterval; | ||||||
|  |     private final int createLimitRefill; | ||||||
|  | 
 | ||||||
|  |     public RateLimitService(final Environment env) { | ||||||
|  |         this.requestLimit = env.getProperty( | ||||||
|  |                 "sebserver.webservice.api.admin.request.limit", Integer.class, 10); | ||||||
|  |         this.requestLimitInterval = | ||||||
|  |                 env.getProperty("sebserver.webservice.api.admin.request.limit.interval.min", Integer.class, 10); | ||||||
|  |         this.requestLimitRefill = | ||||||
|  |                 env.getProperty("sebserver.webservice.api.admin.request.limit.refill", Integer.class, 2); | ||||||
|  | 
 | ||||||
|  |         this.createLimit = env.getProperty( | ||||||
|  |                 "sebserver.webservice.api.admin.create.limit", Integer.class, 10); | ||||||
|  |         this.createLimitInterval = | ||||||
|  |                 env.getProperty("sebserver.webservice.api.admin.create.limit.interval.min", Integer.class, 3600); | ||||||
|  |         this.createLimitRefill = | ||||||
|  |                 env.getProperty("sebserver.webservice.api.admin.create.limit.refill", Integer.class, 10); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public LocalBucket createRequestLimitBucker() { | ||||||
|  |         final Bandwidth limit = Bandwidth.classic( | ||||||
|  |                 this.requestLimit, | ||||||
|  |                 Refill.intervally(this.requestLimitRefill, Duration.ofMinutes(this.requestLimitInterval))); | ||||||
|  |         return Bucket4j.builder() | ||||||
|  |                 .addLimit(limit) | ||||||
|  |                 .build(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public LocalBucket createCreationLimitBucker() { | ||||||
|  |         final Bandwidth limit = Bandwidth.classic( | ||||||
|  |                 this.createLimit, | ||||||
|  |                 Refill.intervally(this.createLimitRefill, Duration.ofMinutes(this.createLimitInterval))); | ||||||
|  |         return Bucket4j.builder() | ||||||
|  |                 .addLimit(limit) | ||||||
|  |                 .build(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -28,6 +28,7 @@ import ch.ethz.seb.sebserver.gbl.api.API; | ||||||
| import ch.ethz.seb.sebserver.gbl.api.APIMessage; | import ch.ethz.seb.sebserver.gbl.api.APIMessage; | ||||||
| import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException; | import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException; | ||||||
| import ch.ethz.seb.sebserver.gbl.api.POSTMapper; | import ch.ethz.seb.sebserver.gbl.api.POSTMapper; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.api.TooManyRequests; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.Domain.USER_ROLE; | import ch.ethz.seb.sebserver.gbl.model.Domain.USER_ROLE; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange; | import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; | import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; | ||||||
|  | @ -38,6 +39,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.InstitutionDAO; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; | 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.dao.UserDAO; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService; | import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService; | ||||||
|  | import io.github.bucket4j.local.LocalBucket; | ||||||
| 
 | 
 | ||||||
| @WebServiceProfile | @WebServiceProfile | ||||||
| @RestController | @RestController | ||||||
|  | @ -47,17 +49,23 @@ public class RegisterUserController { | ||||||
|     private final UserActivityLogDAO userActivityLogDAO; |     private final UserActivityLogDAO userActivityLogDAO; | ||||||
|     private final UserDAO userDAO; |     private final UserDAO userDAO; | ||||||
|     private final BeanValidationService beanValidationService; |     private final BeanValidationService beanValidationService; | ||||||
|  |     private final LocalBucket requestRateLimitBucket; | ||||||
|  |     private final LocalBucket createRateLimitBucket; | ||||||
| 
 | 
 | ||||||
|     protected RegisterUserController( |     protected RegisterUserController( | ||||||
|             final InstitutionDAO institutionDAO, |             final InstitutionDAO institutionDAO, | ||||||
|             final UserActivityLogDAO userActivityLogDAO, |             final UserActivityLogDAO userActivityLogDAO, | ||||||
|             final UserDAO userDAO, |             final UserDAO userDAO, | ||||||
|             final BeanValidationService beanValidationService, |             final BeanValidationService beanValidationService, | ||||||
|  |             final RateLimitService rateLimitService, | ||||||
|             @Qualifier(WebSecurityConfig.USER_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder userPasswordEncoder) { |             @Qualifier(WebSecurityConfig.USER_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder userPasswordEncoder) { | ||||||
| 
 | 
 | ||||||
|         this.userActivityLogDAO = userActivityLogDAO; |         this.userActivityLogDAO = userActivityLogDAO; | ||||||
|         this.userDAO = userDAO; |         this.userDAO = userDAO; | ||||||
|         this.beanValidationService = beanValidationService; |         this.beanValidationService = beanValidationService; | ||||||
|  | 
 | ||||||
|  |         this.requestRateLimitBucket = rateLimitService.createRequestLimitBucker(); | ||||||
|  |         this.createRateLimitBucket = rateLimitService.createCreationLimitBucker(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @RequestMapping( |     @RequestMapping( | ||||||
|  | @ -68,6 +76,10 @@ public class RegisterUserController { | ||||||
|             @RequestParam final MultiValueMap<String, String> allRequestParams, |             @RequestParam final MultiValueMap<String, String> allRequestParams, | ||||||
|             final HttpServletRequest request) { |             final HttpServletRequest request) { | ||||||
| 
 | 
 | ||||||
|  |         if (!this.requestRateLimitBucket.tryConsume(1)) { | ||||||
|  |             throw new TooManyRequests(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         final POSTMapper postMap = new POSTMapper(allRequestParams, request.getQueryString()) |         final POSTMapper postMap = new POSTMapper(allRequestParams, request.getQueryString()) | ||||||
|                 .putIfAbsent(USER_ROLE.REFERENCE_NAME, UserRole.EXAM_SUPPORTER.name()); |                 .putIfAbsent(USER_ROLE.REFERENCE_NAME, UserRole.EXAM_SUPPORTER.name()); | ||||||
|         final UserMod userMod = new UserMod(null, postMap); |         final UserMod userMod = new UserMod(null, postMap); | ||||||
|  | @ -88,8 +100,11 @@ public class RegisterUserController { | ||||||
|                         throw new APIMessageException(errors); |                         throw new APIMessageException(errors); | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     return userAccount; |                     if (!this.createRateLimitBucket.tryConsume(1)) { | ||||||
|  |                         throw new TooManyRequests(TooManyRequests.Code.REGISTRATION); | ||||||
|  |                     } | ||||||
| 
 | 
 | ||||||
|  |                     return userAccount; | ||||||
|                 }) |                 }) | ||||||
|                 .flatMap(this.userDAO::createNew) |                 .flatMap(this.userDAO::createNew) | ||||||
|                 .flatMap(account -> this.userDAO.setActive(account, true)) |                 .flatMap(account -> this.userDAO.setActive(account, true)) | ||||||
|  |  | ||||||
|  | @ -44,6 +44,12 @@ sebserver.webservice.api.admin.clientId=guiClient | ||||||
| sebserver.webservice.api.admin.endpoint=/admin-api/v1 | sebserver.webservice.api.admin.endpoint=/admin-api/v1 | ||||||
| sebserver.webservice.api.admin.accessTokenValiditySeconds=3600 | sebserver.webservice.api.admin.accessTokenValiditySeconds=3600 | ||||||
| sebserver.webservice.api.admin.refreshTokenValiditySeconds=25200 | sebserver.webservice.api.admin.refreshTokenValiditySeconds=25200 | ||||||
|  | sebserver.webservice.api.admin.request.limit=10 | ||||||
|  | sebserver.webservice.api.admin.request.limit.interval.min=10 | ||||||
|  | sebserver.webservice.api.admin.request.limit.refill=2 | ||||||
|  | sebserver.webservice.api.admin.create.limit=10 | ||||||
|  | sebserver.webservice.api.admin.create.limit.interval.min=3600 | ||||||
|  | sebserver.webservice.api.admin.create.limit.refill=10 | ||||||
| sebserver.webservice.api.exam.config.init.permittedProcesses=config/initialPermittedProcesses.xml | sebserver.webservice.api.exam.config.init.permittedProcesses=config/initialPermittedProcesses.xml | ||||||
| sebserver.webservice.api.exam.config.init.prohibitedProcesses=config/initialProhibitedProcesses.xml | sebserver.webservice.api.exam.config.init.prohibitedProcesses=config/initialProhibitedProcesses.xml | ||||||
| sebserver.webservice.api.exam.endpoint=/exam-api | sebserver.webservice.api.exam.endpoint=/exam-api | ||||||
|  | @ -57,3 +63,4 @@ sebserver.webservice.api.pagination.maxPageSize=500 | ||||||
| sebserver.webservice.lms.openedx.api.token.request.paths=/oauth2/access_token | sebserver.webservice.lms.openedx.api.token.request.paths=/oauth2/access_token | ||||||
| sebserver.webservice.lms.moodle.api.token.request.paths=/login/token.php | sebserver.webservice.lms.moodle.api.token.request.paths=/login/token.php | ||||||
| sebserver.webservice.lms.address.alias= | sebserver.webservice.lms.address.alias= | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | @ -107,6 +107,8 @@ sebserver.error.save.entity=Failed to save {0}.<br/> Please try again or contact | ||||||
| sebserver.error.exam.seb.restriction=<br/><br/>Failed to automatically set Safe Exam Browser restriction on/off for this exam on the corresponding LMS.<br/> Please check the LMS Setup and try again or contact a system administrator if this error persists | sebserver.error.exam.seb.restriction=<br/><br/>Failed to automatically set Safe Exam Browser restriction on/off for this exam on the corresponding LMS.<br/> Please check the LMS Setup and try again or contact a system administrator if this error persists | ||||||
| sebserver.error.import=Failed to import {0}.<br/> Please try again or contact a system administrator if this error persists | sebserver.error.import=Failed to import {0}.<br/> Please try again or contact a system administrator if this error persists | ||||||
| sebserver.error.logout=Failed to logout properly.<br/> Please try again or contact a system administrator if this error persists | sebserver.error.logout=Failed to logout properly.<br/> Please try again or contact a system administrator if this error persists | ||||||
|  | sebserver.error.tooManyRequests.INCOMMING=This request has been blocked as there are too many incoming request at the moment for this page.<br/><br/> Please try again later. | ||||||
|  | sebserver.error.tooManyRequests.REGISTRATION=This request has been blocked as the maximum user registration per day limit has reached.<br/><br/> Please try again next day or call an administrator to create a user account. | ||||||
| ################################ | ################################ | ||||||
| # Login Page | # Login Page | ||||||
| ################################ | ################################ | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 anhefti
						anhefti