SEBSERV-178 added request reate limits for user reg page
This commit is contained in:
parent
ed9ded57db
commit
c0c5a4556b
11 changed files with 196 additions and 11 deletions
5
pom.xml
5
pom.xml
|
@ -341,6 +341,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
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;
|
||||||
|
@ -38,6 +39,8 @@ public class FormHandle<T extends Entity> {
|
||||||
private static final Logger log = LoggerFactory.getLogger(FormHandle.class);
|
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;
|
||||||
|
@ -147,14 +150,32 @@ public class FormHandle<T extends Entity> {
|
||||||
fieldAccessor -> showValidationError(fieldAccessor, fve)));
|
fieldAccessor -> showValidationError(fieldAccessor, fve)));
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
log.error("Unexpected error while trying to post form: {}", error.getMessage());
|
log.error("Unexpected error while trying to post form: {}", error.getMessage());
|
||||||
final EntityType resultType = this.post.getEntityType();
|
|
||||||
if (resultType != null) {
|
try {
|
||||||
this.pageContext.notifySaveError(resultType, error);
|
|
||||||
} else {
|
if (error instanceof TooManyRequests) {
|
||||||
this.pageContext.notifyError(
|
final TooManyRequests.Code code = ((TooManyRequests) error).code;
|
||||||
new LocTextKey(PageContext.GENERIC_SAVE_ERROR_TEXT_KEY, Constants.EMPTY_NOTE),
|
if (code != null) {
|
||||||
error);
|
this.pageContext.publishInfo(new LocTextKey(MESSAGE_TOO_MANY_REQUESTS_TEXT.name + "." + code));
|
||||||
|
} else {
|
||||||
|
this.pageContext.publishInfo(MESSAGE_TOO_MANY_REQUESTS_TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final EntityType resultType = this.post.getEntityType();
|
||||||
|
if (resultType != null) {
|
||||||
|
this.pageContext.notifySaveError(resultType, error);
|
||||||
|
} else {
|
||||||
|
this.pageContext.notifyError(
|
||||||
|
new LocTextKey(PageContext.GENERIC_SAVE_ERROR_TEXT_KEY, Constants.EMPTY_NOTE),
|
||||||
|
error);
|
||||||
|
}
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.error("Failed to handle error: ", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -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
|
||||||
|
@ -56,4 +62,5 @@ sebserver.webservice.api.pagination.maxPageSize=500
|
||||||
# comma separated list of known possible OpenEdX API access token request endpoints
|
# comma separated list of known possible OpenEdX API access token request endpoints
|
||||||
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…
Reference in a new issue