Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • arsnova/arsnova-backend
  • pcvl72/arsnova-backend
  • tksl38/arsnova-backend
3 results
Show changes
Showing
with 1329 additions and 1635 deletions
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import de.thm.arsnova.model.Comment;
import de.thm.arsnova.service.CommentService;
@RestController
@RequestMapping(CommentController.REQUEST_MAPPING)
public class CommentController extends AbstractEntityController<Comment> {
protected static final String REQUEST_MAPPING = "/comment";
private CommentService commentService;
public CommentController(final CommentService commentService) {
super(commentService);
this.commentService = commentService;
}
@Override
protected String getMapping() {
return REQUEST_MAPPING;
}
}
/*
* Copyright (C) 2014 THM webMedia
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* This file is part of ARSnova.
*
* ARSnova is free software: you can redistribute it and/or modify
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova is distributed in the hope that it will be useful,
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
......@@ -16,135 +15,90 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller;
import java.util.ArrayList;
import java.util.HashMap;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import java.util.List;
import java.util.Map;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* The ConfigurationController provides frontend clients with information necessary to correctly interact with the
* backend and other frontends as well as settings for ARSnova. The the alternative /arsnova-config route is necessary
* in case the backend application is deployed as root context.
*/
@Controller
@RequestMapping({"/configuration", "/arsnova-config"})
public class ConfigurationController extends AbstractController {
@Value("${security.guest.enabled}")
private String guestEnabled;
public static final Logger LOGGER = LoggerFactory
.getLogger(ConfigurationController.class);
@Value("${customization.path}")
private String customizationPath;
@Value("${mobile.path}")
private String mobilePath;
@Value("${presenter.path}")
private String presenterPath;
@Value("${links.overlay.url}")
private String overlayUrl;
@Value("${links.organization.url}")
private String organizationUrl;
@Value("${links.imprint.url}")
private String imprintUrl;
@Value("${links.privacy-policy.url}")
private String privacyPolicyUrl;
@Value("${links.documentation.url}")
private String documentationUrl;
@Value("${links.presenter-documentation.url}")
private String presenterDocumentationUrl;
@Value("${features.mathjax.enabled:true}")
private String mathJaxEnabled;
@Value("${features.markdown.enabled:false}")
private String markdownEnabled;
@Value("${features.learning-progress.enabled:false}")
private String learningProgressEnabled;
@Value("${features.students-own-questions.enabled:false}")
private String studentsOwnQuestions;
@Value("${features.question-format.flashcard.enabled:false}")
private String flashcardEnabled;
@Value("${features.question-format.grid-square.enabled:false}")
private String gridSquareEnabled;
@Value("${question.answer-option-limit:8}")
private String answerOptionLimit;
@Value("${question.parse-answer-option-formatting:false}")
private String parseAnswerOptionFormatting;
@RequestMapping(method = RequestMethod.GET)
@ResponseBody
public final HashMap<String, Object> getConfiguration(HttpServletRequest request) {
HashMap<String, Object> config = new HashMap<String, Object>();
HashMap<String, Boolean> features = new HashMap<String, Boolean>();
import org.springframework.web.bind.annotation.RestController;
import de.thm.arsnova.config.SecurityConfig;
import de.thm.arsnova.config.properties.AuthenticationProviderProperties;
import de.thm.arsnova.config.properties.FeatureProperties;
import de.thm.arsnova.config.properties.UiProperties;
import de.thm.arsnova.model.AuthenticationProvider;
import de.thm.arsnova.model.Configuration;
@RestController
@RequestMapping(ConfigurationController.REQUEST_MAPPING)
@EnableConfigurationProperties(UiProperties.class)
public class ConfigurationController {
protected static final String REQUEST_MAPPING = "/configuration";
private AuthenticationProviderProperties providerProperties;
private UiProperties uiProperties;
private FeatureProperties featureProperties;
private List<AuthenticationProvider> authenticationProviders;
private Map<String, Object> featureConfig;
public ConfigurationController(
final AuthenticationProviderProperties authenticationProviderProperties,
final UiProperties uiProperties,
final FeatureProperties featureProperties) {
this.providerProperties = authenticationProviderProperties;
this.uiProperties = uiProperties;
this.featureProperties = featureProperties;
buildAuthenticationProviderConfig();
buildFeatureConfig();
}
/* The API path could be unknown to the client in case the request was forwarded */
config.put("apiPath", request.getContextPath());
@GetMapping
public Configuration get() {
final Configuration configuration = new Configuration();
configuration.setAuthenticationProviders(authenticationProviders);
configuration.setUi(uiProperties.getUi());
configuration.setFeatures(featureConfig);
if (!"".equals(customizationPath)) {
config.put("customizationPath", customizationPath);
}
if (!"".equals(mobilePath)) {
config.put("mobilePath", mobilePath);
}
if (!"".equals(presenterPath)) {
config.put("presenterPath", presenterPath);
}
return configuration;
}
if (!"".equals(documentationUrl)) {
config.put("documentationUrl", documentationUrl);
private void buildAuthenticationProviderConfig() {
this.authenticationProviders = new ArrayList<>();
if (providerProperties.getGuest().isEnabled()) {
authenticationProviders.add(new AuthenticationProvider("guest", providerProperties.getGuest()));
}
if (!"".equals(presenterDocumentationUrl)) {
config.put("presenterDocumentationUrl", presenterDocumentationUrl);
if (providerProperties.getRegistered().isEnabled()) {
authenticationProviders.add(new AuthenticationProvider(
SecurityConfig.INTERNAL_PROVIDER_ID, providerProperties.getRegistered()));
}
if (!"".equals(overlayUrl)) {
config.put("overlayUrl", overlayUrl);
if (!providerProperties.getLdap().isEmpty() && providerProperties.getLdap().get(0).isEnabled()) {
authenticationProviders.add(new AuthenticationProvider(
SecurityConfig.LDAP_PROVIDER_ID, providerProperties.getLdap().get(0)));
}
if (!"".equals(organizationUrl)) {
config.put("organizationUrl", organizationUrl);
if (!providerProperties.getOidc().isEmpty() && providerProperties.getOidc().get(0).isEnabled()) {
authenticationProviders.add(new AuthenticationProvider(
SecurityConfig.OIDC_PROVIDER_ID, providerProperties.getOidc().get(0)));
}
if (!"".equals(imprintUrl)) {
config.put("imprintUrl", imprintUrl);
if (providerProperties.getSaml().isEnabled()) {
authenticationProviders.add(new AuthenticationProvider(
SecurityConfig.SAML_PROVIDER_ID, providerProperties.getSaml()));
}
if (!"".equals(privacyPolicyUrl)) {
config.put("privacyPolicyUrl", privacyPolicyUrl);
if (providerProperties.getCas().isEnabled()) {
authenticationProviders.add(new AuthenticationProvider(
SecurityConfig.CAS_PROVIDER_ID, providerProperties.getCas()));
}
}
config.put("answerOptionLimit", Integer.valueOf(answerOptionLimit));
config.put("parseAnswerOptionFormatting", Boolean.valueOf(parseAnswerOptionFormatting));
config.put("features", features);
features.put("mathJax", "true".equals(mathJaxEnabled));
features.put("markdown", "true".equals(markdownEnabled));
features.put("learningProgress", "true".equals(learningProgressEnabled));
features.put("studentsOwnQuestions", "true".equals(studentsOwnQuestions));
features.put("flashcard", "true".equals(flashcardEnabled));
features.put("gridSquare", "true".equals(gridSquareEnabled));
return config;
private void buildFeatureConfig() {
this.featureConfig = new HashMap<>();
featureConfig.put("contents", featureProperties.getContents());
featureConfig.put("comments", featureProperties.getComments());
featureConfig.put("liveFeedback", featureProperties.getLiveFeedback());
featureConfig.put("contentPool", featureProperties.getContentPool());
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import de.thm.arsnova.model.AnswerStatistics;
import de.thm.arsnova.model.Content;
import de.thm.arsnova.service.AnswerService;
import de.thm.arsnova.service.ContentService;
@RestController
@RequestMapping(ContentController.REQUEST_MAPPING)
public class ContentController extends AbstractEntityController<Content> {
protected static final String REQUEST_MAPPING = "/content";
private static final String GET_ANSWER_STATISTICS_MAPPING = DEFAULT_ID_MAPPING + "/stats";
private ContentService contentService;
private AnswerService answerService;
public ContentController(final ContentService contentService, final AnswerService answerService) {
super(contentService);
this.contentService = contentService;
this.answerService = answerService;
}
@Override
protected String getMapping() {
return REQUEST_MAPPING;
}
@GetMapping(GET_ANSWER_STATISTICS_MAPPING)
public AnswerStatistics getAnswerStatistics(@PathVariable final String id) {
return answerService.getAllStatistics(id);
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller;
import java.util.Map;
import javax.naming.OperationNotSupportedException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.ektorp.DocumentNotFoundException;
import org.slf4j.event.Level;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import de.thm.arsnova.model.EntityValidationException;
import de.thm.arsnova.web.exceptions.BadRequestException;
import de.thm.arsnova.web.exceptions.ForbiddenException;
import de.thm.arsnova.web.exceptions.NoContentException;
import de.thm.arsnova.web.exceptions.NotFoundException;
import de.thm.arsnova.web.exceptions.NotImplementedException;
import de.thm.arsnova.web.exceptions.PayloadTooLargeException;
import de.thm.arsnova.web.exceptions.PreconditionFailedException;
import de.thm.arsnova.web.exceptions.UnauthorizedException;
/**
* Translates exceptions into HTTP status codes.
*/
@ControllerAdvice
public class ControllerExceptionHandler extends ResponseEntityExceptionHandler {
private ControllerExceptionHelper helper;
public ControllerExceptionHandler(final ControllerExceptionHelper helper) {
this.helper = helper;
}
@ExceptionHandler(NoContentException.class)
@ResponseBody
@ResponseStatus(HttpStatus.NO_CONTENT)
public Map<String, Object> handleNoContentException(final Exception e, final HttpServletRequest request) {
return helper.handleException(e, Level.TRACE);
}
@ExceptionHandler(NotFoundException.class)
@ResponseBody
@ResponseStatus(HttpStatus.NOT_FOUND)
public Map<String, Object> handleNotFoundException(final Exception e, final HttpServletRequest request) {
return helper.handleException(e, Level.TRACE);
}
@ExceptionHandler(UnauthorizedException.class)
@ResponseBody
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public Map<String, Object> handleUnauthorizedException(final Exception e, final HttpServletRequest request) {
return helper.handleException(e, Level.TRACE);
}
@ExceptionHandler(AuthenticationException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ResponseBody
public Map<String, Object> handleAuthenticationExceptionException(
final Exception e, final HttpServletRequest request) {
return helper.handleException(e, Level.DEBUG);
}
@ExceptionHandler(AccessDeniedException.class)
@ResponseBody
public Map<String, Object> handleAccessDeniedException(
final Exception e,
final HttpServletRequest request,
final HttpServletResponse response) {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null
|| authentication.getPrincipal() == null
|| authentication instanceof AnonymousAuthenticationToken) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
} else {
response.setStatus(HttpStatus.FORBIDDEN.value());
}
return helper.handleException(e, Level.DEBUG);
}
@ExceptionHandler(ForbiddenException.class)
@ResponseBody
@ResponseStatus(HttpStatus.FORBIDDEN)
public Map<String, Object> handleForbiddenException(final Exception e, final HttpServletRequest request) {
return helper.handleException(e, Level.DEBUG);
}
@ExceptionHandler(BadRequestException.class)
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, Object> handleBadRequestException(final Exception e, final HttpServletRequest request) {
return helper.handleException(e, Level.DEBUG);
}
@ExceptionHandler(EntityValidationException.class)
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, Object> handleEntityValidationException(
final EntityValidationException e, final HttpServletRequest request) {
return helper.handleException(e, Level.DEBUG);
}
@ExceptionHandler(PreconditionFailedException.class)
@ResponseBody
@ResponseStatus(HttpStatus.PRECONDITION_FAILED)
public Map<String, Object> handlePreconditionFailedException(final Exception e, final HttpServletRequest request) {
return helper.handleException(e, Level.DEBUG);
}
@ExceptionHandler({NotImplementedException.class, OperationNotSupportedException.class})
@ResponseBody
@ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
public Map<String, Object> handleNotImplementedException(final Exception e, final HttpServletRequest request) {
return helper.handleException(e, Level.DEBUG);
}
@ExceptionHandler(PayloadTooLargeException.class)
@ResponseBody
@ResponseStatus(HttpStatus.PAYLOAD_TOO_LARGE)
public Map<String, Object> handlePayloadTooLargeException(final Exception e, final HttpServletRequest request) {
return helper.handleException(e, Level.DEBUG);
}
/* FIXME: Wrap persistance Exceptions - do not handle persistance Exceptions at the controller layer */
@ExceptionHandler(DocumentNotFoundException.class)
@ResponseBody
@ResponseStatus(HttpStatus.NOT_FOUND)
public Map<String, Object> handleDocumentNotFoundException(final Exception e, final HttpServletRequest request) {
return helper.handleException(e, Level.TRACE);
}
@Override
protected ResponseEntity<Object> handleExceptionInternal(final Exception ex, final Object body,
final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
return new ResponseEntity<>(helper.handleException(ex, Level.TRACE), headers, status);
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller;
import java.util.HashMap;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.event.Level;
import org.springframework.stereotype.Component;
import de.thm.arsnova.config.properties.SystemProperties;
@Component
public class ControllerExceptionHelper {
private static final Logger logger = LoggerFactory.getLogger(ControllerExceptionHelper.class);
/* Since exception messages might contain sensitive data, they are not exposed by default. */
private boolean exposeMessages;
public ControllerExceptionHelper(final SystemProperties systemProperties) {
this.exposeMessages = systemProperties.getApi().isExposeErrorMessages();
}
public Map<String, Object> handleException(@NonNull final Throwable e, @NonNull final Level level) {
final String message = e.getMessage() != null ? e.getMessage() : "";
log(level, message, e);
final Map<String, Object> result = new HashMap<>();
result.put("errorType", e.getClass().getSimpleName());
if (exposeMessages) {
result.put("errorMessage", e.getMessage());
}
return result;
}
private void log(final Level level, final String message, final Throwable e) {
switch (level) {
case ERROR:
logger.error(message, e);
break;
case WARN:
logger.warn(message, e);
break;
case INFO:
logger.info(message, e);
break;
case DEBUG:
logger.debug(message, e);
break;
case TRACE:
logger.trace(message, e);
break;
default:
break;
}
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.event.Level;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
@ControllerAdvice
public class DefaultControllerExceptionHandler {
private ControllerExceptionHelper helper;
public DefaultControllerExceptionHandler(final ControllerExceptionHelper helper) {
this.helper = helper;
}
@ExceptionHandler
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Map<String, Object> defaultExceptionHandler(
final Exception e,
final HttpServletRequest req
) throws Exception {
/* If the exception is annotated with @ResponseStatus rethrow it and let
* the framework handle it.
* See https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc. */
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
throw e;
}
return helper.handleException(e, Level.ERROR);
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.AbstractMappingJacksonResponseBodyAdvice;
import org.springframework.web.util.UriComponentsBuilder;
import de.thm.arsnova.model.serialization.View;
/**
* This {@link ControllerAdvice} applies a {@link View} based on the
* <code>view</code> query parameter which is used by
* {@link com.fasterxml.jackson.annotation.JsonView} for serialization and makes
* sure that the user is authorized.
*
* @author Daniel Gerhardt
*/
@ControllerAdvice
public class JsonViewControllerAdvice extends AbstractMappingJacksonResponseBodyAdvice {
private static final String VIEW_PARAMETER = "view";
private static final Logger logger = LoggerFactory.getLogger(JsonViewControllerAdvice.class);
@Override
protected void beforeBodyWriteInternal(final MappingJacksonValue bodyContainer,
final MediaType contentType, final MethodParameter returnType,
final ServerHttpRequest request, final ServerHttpResponse response) {
/* TODO: Why does @ControllerAdvice(assignableTypes = AbstractEntityController.class) not work? */
if (!AbstractEntityController.class.isAssignableFrom(returnType.getContainingClass())) {
return;
}
final List<String> viewList = UriComponentsBuilder.fromUri(request.getURI()).build()
.getQueryParams().getOrDefault(VIEW_PARAMETER, Collections.emptyList());
if (viewList.isEmpty()) {
return;
}
final String view = viewList.get(0);
logger.debug("'{}' parameter found in request URI: {}", VIEW_PARAMETER, view);
if (bodyContainer.getValue() instanceof Collection) {
logger.warn("'{}' parameter is currently not supported for listing endpoints.", VIEW_PARAMETER);
}
tryAccess(bodyContainer.getValue(), view);
switch (view) {
case "owner":
bodyContainer.setSerializationView(View.Owner.class);
break;
case "admin":
bodyContainer.setSerializationView(View.Admin.class);
break;
default:
return;
}
}
@PreAuthorize("hasPermission(#targetDomainObject, #permission)")
protected void tryAccess(final Object targetDomainObject, final Object permission) {
/* Access check is done by aspect. No additional implementation needed. */
}
}
/*
* Copyright (C) 2012 THM webMedia
*
* This file is part of ARSnova.
*
* ARSnova is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import de.thm.arsnova.entities.Answer;
import de.thm.arsnova.entities.Question;
import de.thm.arsnova.exceptions.BadRequestException;
import de.thm.arsnova.exceptions.NoContentException;
import de.thm.arsnova.exceptions.NotFoundException;
import de.thm.arsnova.services.IQuestionService;
import de.thm.arsnova.web.DeprecatedApi;
@RestController
@RequestMapping("/lecturerquestion")
public class LecturerQuestionController extends AbstractController {
public static final Logger LOGGER = LoggerFactory.getLogger(LecturerQuestionController.class);
@Autowired
private IQuestionService questionService;
@RequestMapping(value = "/{questionId}", method = RequestMethod.GET)
public final Question getQuestion(@PathVariable final String questionId) {
final Question question = questionService.getQuestion(questionId);
if (question != null) {
return question;
}
throw new NotFoundException();
}
@RequestMapping(value = "/", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
public final Question postQuestion(@RequestBody final Question question) {
if (questionService.saveQuestion(question) != null) {
return question;
}
throw new BadRequestException();
}
@RequestMapping(value = "/{questionId}", method = RequestMethod.PUT)
public final Question updateQuestion(
@PathVariable final String questionId,
@RequestBody final Question question
) {
try {
return questionService.update(question);
} catch (final Exception e) {
throw new BadRequestException();
}
}
@RequestMapping(value = "/{questionId}/publish", method = RequestMethod.POST)
public final void publishQuestion(
@PathVariable final String questionId,
@RequestParam(required = false) final Boolean publish,
@RequestBody final Question question
) {
if (publish != null) {
question.setActive(publish);
}
questionService.update(question);
}
@RequestMapping(value = "/publish", method = RequestMethod.POST)
public final void publishAllQuestions(
@RequestParam final String sessionkey,
@RequestParam(required = false) final Boolean publish
) {
boolean p = true;
if (publish != null) {
p = publish;
}
questionService.publishAll(sessionkey, p);
}
@RequestMapping(value = "/{questionId}/publishstatistics", method = RequestMethod.POST)
public final void publishStatistics(
@PathVariable final String questionId,
@RequestParam(required = false) final Boolean showStatistics,
@RequestBody final Question question
) {
if (showStatistics != null) {
question.setShowStatistic(showStatistics);
}
questionService.update(question);
}
@RequestMapping(value = "/{questionId}/publishcorrectanswer", method = RequestMethod.POST)
public final void publishCorrectAnswer(
@PathVariable final String questionId,
@RequestParam(required = false) final Boolean showCorrectAnswer,
@RequestBody final Question question
) {
if (showCorrectAnswer != null) {
question.setShowAnswer(showCorrectAnswer);
}
questionService.update(question);
}
@RequestMapping(value = "/", method = RequestMethod.GET)
public final List<Question> getSkillQuestions(
@RequestParam final String sessionkey,
@RequestParam(value = "lecturequestionsonly", defaultValue = "false") final boolean lectureQuestionsOnly,
@RequestParam(value = "flashcardsonly", defaultValue = "false") final boolean flashcardsOnly,
@RequestParam(value = "preparationquestionsonly", defaultValue = "false") final boolean preparationQuestionsOnly,
final HttpServletResponse response
) {
List<Question> questions;
if (lectureQuestionsOnly) {
questions = questionService.getLectureQuestions(sessionkey);
} else if (flashcardsOnly) {
questions = questionService.getFlashcards(sessionkey);
} else if (preparationQuestionsOnly) {
questions = questionService.getPreparationQuestions(sessionkey);
} else {
questions = questionService.getSkillQuestions(sessionkey);
}
if (questions == null || questions.isEmpty()) {
response.setStatus(HttpStatus.NO_CONTENT.value());
return null;
}
return questions;
}
@RequestMapping(value = { "/" }, method = RequestMethod.DELETE)
public final void deleteSkillQuestions(
@RequestParam final String sessionkey,
@RequestParam(value = "lecturequestionsonly", defaultValue = "false") final boolean lectureQuestionsOnly,
@RequestParam(value = "flashcardsonly", defaultValue = "false") final boolean flashcardsOnly,
@RequestParam(value = "preparationquestionsonly", defaultValue = "false") final boolean preparationQuestionsOnly,
final HttpServletResponse response
) {
if (lectureQuestionsOnly) {
questionService.deleteLectureQuestions(sessionkey);
} else if (flashcardsOnly) {
questionService.deleteFlashcards(sessionkey);
} else if (preparationQuestionsOnly) {
questionService.deletePreparationQuestions(sessionkey);
} else {
questionService.deleteAllQuestions(sessionkey);
}
}
@DeprecatedApi
@RequestMapping(value = "/count", method = RequestMethod.GET)
public final int getSkillQuestionCount(
@RequestParam final String sessionkey,
@RequestParam(value = "lecturequestionsonly", defaultValue = "false") final boolean lectureQuestionsOnly,
@RequestParam(value = "flashcardsonly", defaultValue = "false") final boolean flashcardsOnly,
@RequestParam(value = "preparationquestionsonly", defaultValue = "false") final boolean preparationQuestionsOnly
) {
if (lectureQuestionsOnly) {
return questionService.getLectureQuestionCount(sessionkey);
} else if (flashcardsOnly) {
return questionService.getFlashcardCount(sessionkey);
} else if (preparationQuestionsOnly) {
return questionService.getPreparationQuestionCount(sessionkey);
} else {
return questionService.getSkillQuestionCount(sessionkey);
}
}
@RequestMapping(value = "/{questionId}", method = RequestMethod.DELETE)
public final void deleteAnswersAndQuestion(
@PathVariable final String questionId
) {
questionService.deleteQuestion(questionId);
}
@DeprecatedApi
@RequestMapping(value = "/unanswered", method = RequestMethod.GET)
public final List<String> getUnAnsweredSkillQuestionIds(
@RequestParam final String sessionkey,
@RequestParam(value = "lecturequestionsonly", defaultValue = "false") final boolean lectureQuestionsOnly,
@RequestParam(value = "preparationquestionsonly", defaultValue = "false") final boolean preparationQuestionsOnly
) {
List<String> answers;
if (lectureQuestionsOnly) {
answers = questionService.getUnAnsweredLectureQuestionIds(sessionkey);
} else if (preparationQuestionsOnly) {
answers = questionService.getUnAnsweredPreparationQuestionIds(sessionkey);
} else {
answers = questionService.getUnAnsweredQuestionIds(sessionkey);
}
if (answers == null || answers.isEmpty()) {
throw new NoContentException();
}
return answers;
}
/**
* returns a JSON document which represents the given answer of a question.
*
* @param sessionKey
* Session Keyword to which the question belongs to
* @param questionId
* CouchDB Question ID for which the given answer should be
* retrieved
* @return JSON Document of {@link Answer} or {@link NotFoundException}
* @throws NotFoundException
* if wrong session, wrong question or no answer was given by
* the current user
* @throws ForbiddenException
* if not logged in
*/
@DeprecatedApi
@RequestMapping(value = "/{questionId}/myanswer", method = RequestMethod.GET)
public final Answer getMyAnswer(
@PathVariable final String questionId,
final HttpServletResponse response
) {
final Answer answer = questionService.getMyAnswer(questionId);
if (answer == null) {
response.setStatus(HttpStatus.NO_CONTENT.value());
return null;
}
return answer;
}
/**
* returns a list of {@link Answer}s encoded as a JSON document for a given
* question id. In this case only {@link Answer} <tt>questionId</tt>,
* <tt>answerText</tt>, <tt>answerSubject</tt> and <tt>answerCount</tt>
* properties are set
*
* @param sessionKey
* Session Keyword to which the question belongs to
* @param questionId
* CouchDB Question ID for which the given answers should be
* retrieved
* @return List<{@link Answer}> or {@link NotFoundException}
* @throws NotFoundException
* if wrong session, wrong question or no answers was given
* @throws ForbiddenException
* if not logged in
*/
@RequestMapping(value = "/{questionId}/answer/", method = RequestMethod.GET)
public final List<Answer> getAnswers(
@PathVariable final String questionId,
@RequestParam(value = "piround", required = false) final Integer piRound,
final HttpServletResponse response
) {
List<Answer> answers = null;
if (null == piRound) {
answers = questionService.getAnswers(questionId);
} else {
if (piRound < 1 || piRound > 2) {
response.setStatus(HttpStatus.BAD_REQUEST.value());
return null;
}
answers = questionService.getAnswers(questionId, piRound);
}
if (answers == null) {
return new ArrayList<Answer>();
}
return answers;
}
@RequestMapping(value = "/{questionId}/answer/", method = RequestMethod.POST)
public final Answer saveAnswer(
@PathVariable final String questionId,
@RequestBody final Answer answer,
final HttpServletResponse response
) {
return questionService.saveAnswer(answer);
}
@RequestMapping(value = "/{questionId}/answer/{answerId}", method = RequestMethod.PUT)
public final Answer updateAnswer(
@PathVariable final String questionId,
@PathVariable final String answerId,
@RequestBody final Answer answer,
final HttpServletResponse response
) {
return questionService.updateAnswer(answer);
}
@RequestMapping(value = "/{questionId}/answer/{answerId}", method = RequestMethod.DELETE)
public final void deleteAnswer(
@PathVariable final String questionId,
@PathVariable final String answerId,
final HttpServletResponse response
) {
questionService.deleteAnswer(questionId, answerId);
}
@RequestMapping(value = "/{questionId}/answer/", method = RequestMethod.DELETE)
public final void deleteAnswers(
@PathVariable final String questionId,
final HttpServletResponse response
) {
questionService.deleteAnswers(questionId);
}
@RequestMapping(value = "/answers", method = RequestMethod.DELETE)
public final void deleteAllQuestionsAnswers(
@RequestParam final String sessionkey,
final HttpServletResponse response
) {
questionService.deleteAllQuestionsAnswers(sessionkey);
}
/**
*
* @param sessionKey
* Session Keyword to which the question belongs to
* @param questionId
* CouchDB Question ID for which the given answers should be
* retrieved
* @return count of answers for given question id
* @throws NotFoundException
* if wrong session or wrong question
* @throws ForbiddenException
* if not logged in
*/
@DeprecatedApi
@RequestMapping(value = "/{questionId}/answercount", method = RequestMethod.GET)
public final int getAnswerCount(@PathVariable final String questionId) {
return questionService.getAnswerCount(questionId);
}
@RequestMapping(value = "/{questionId}/freetextanswer/", method = RequestMethod.GET)
public final List<Answer> getFreetextAnswers(@PathVariable final String questionId) {
return questionService.getFreetextAnswers(questionId);
}
@DeprecatedApi
@RequestMapping(value = "/myanswers", method = RequestMethod.GET)
public final List<Answer> getMyAnswers(@RequestParam final String sessionkey) {
return questionService.getMyAnswers(sessionkey);
}
@DeprecatedApi
@RequestMapping(value = "/answercount", method = RequestMethod.GET)
public final int getTotalAnswerCount(
@RequestParam final String sessionkey,
@RequestParam(value = "lecturequestionsonly", defaultValue = "false") final boolean lectureQuestionsOnly,
@RequestParam(value = "preparationquestionsonly", defaultValue = "false") final boolean preparationQuestionsOnly
) {
if (lectureQuestionsOnly) {
return questionService.countLectureQuestionAnswers(sessionkey);
} else if (preparationQuestionsOnly) {
return questionService.countPreparationQuestionAnswers(sessionkey);
} else {
return questionService.getTotalAnswerCount(sessionkey);
}
}
}
/*
* Copyright (C) 2012 THM webMedia
*
* This file is part of ARSnova.
*
* ARSnova is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import de.thm.arsnova.services.IQuestionService;
import de.thm.arsnova.web.DeprecatedApi;
@Controller
public class LegacyController extends AbstractController {
public static final Logger LOGGER = LoggerFactory.getLogger(LegacyController.class);
@Autowired
private IQuestionService questionService;
/* specific routes */
@DeprecatedApi
@RequestMapping(value = "/session/mysessions", method = RequestMethod.GET)
public final String redirectSessionMy() {
return "forward:/session/?ownedonly=true";
}
@DeprecatedApi
@RequestMapping(value = "/session/visitedsessions", method = RequestMethod.GET)
public final String redirectSessionVisited() {
return "forward:/session/?visitedonly=true";
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/question")
public final String redirectQuestionByLecturer(@PathVariable final String sessionKey) {
return String.format("forward:/lecturerquestion/?sessionkey=%s", sessionKey);
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/skillquestions", method = RequestMethod.GET)
public final String redirectQuestionByLecturerList(@PathVariable final String sessionKey) {
return String.format("forward:/lecturerquestion/?sessionkey=%s", sessionKey);
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/skillquestioncount", method = RequestMethod.GET)
public final String redirectQuestionByLecturerCount(@PathVariable final String sessionKey) {
return String.format("forward:/lecturerquestion/count?sessionkey=%s", sessionKey);
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/answercount", method = RequestMethod.GET)
public final String redirectQuestionByLecturerAnswerCount(@PathVariable final String sessionKey) {
return String.format("forward:/lecturerquestion/answercount?sessionkey=%s", sessionKey);
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/unanswered", method = RequestMethod.GET)
public final String redirectQuestionByLecturerUnnsweredCount(@PathVariable final String sessionKey) {
return String.format("forward:/lecturerquestion/answercount?sessionkey=%s", sessionKey);
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/myanswers", method = RequestMethod.GET)
public final String redirectQuestionByLecturerMyAnswers(@PathVariable final String sessionKey) {
return String.format("forward:/lecturerquestion/myanswers?sessionkey=%s", sessionKey);
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/interposed")
public final String redirectQuestionByAudience(@PathVariable final String sessionKey) {
return String.format("forward:/audiencequestion/?sessionkey=%s", sessionKey);
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/interposed", method = RequestMethod.DELETE)
@ResponseBody
public final void deleteAllInterposedQuestions(@PathVariable final String sessionKey) {
questionService.deleteAllInterposedQuestions(sessionKey);
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/interposedcount", method = RequestMethod.GET)
public final String redirectQuestionByAudienceCount(@PathVariable final String sessionKey) {
return String.format("forward:/audiencequestion/count?sessionkey=%s", sessionKey);
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/interposedreadingcount", method = RequestMethod.GET)
public final String redirectQuestionByAudienceReadCount(@PathVariable final String sessionKey) {
return String.format("forward:/audiencequestion/readcount?sessionkey=%s", sessionKey);
}
/* generalized routes */
@DeprecatedApi
@RequestMapping(value = { "/session/{sessionKey}/question/{arg1}", "/session/{sessionKey}/questions/{arg1}" })
public final String redirectQuestionByLecturerWithOneArgument(
@PathVariable final String sessionKey,
@PathVariable final String arg1
) {
return String.format("forward:/lecturerquestion/%s/?sessionkey=%s", arg1, sessionKey);
}
@DeprecatedApi
@RequestMapping(
value = { "/session/{sessionKey}/question/{arg1}/{arg2}", "/session/{sessionKey}/questions/{arg1}/{arg2}" }
)
public final String redirectQuestionByLecturerWithTwoArguments(
@PathVariable final String sessionKey,
@PathVariable final String arg1,
@PathVariable final String arg2
) {
return String.format("forward:/lecturerquestion/%s/%s/?sessionkey=%s", arg1, arg2, sessionKey);
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/interposed/{arg1}")
public final String redirectQuestionByAudienceWithOneArgument(
@PathVariable final String sessionKey,
@PathVariable final String arg1
) {
return String.format("forward:/audiencequestion/%s/?sessionkey=%s", arg1, sessionKey);
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/interposed/{arg1}/{arg2}")
public final String redirectQuestionByAudienceWithTwoArguments(
@PathVariable final String sessionKey,
@PathVariable final String arg1,
@PathVariable final String arg2
) {
return String.format("forward:/audiencequestion/%s/%s/?sessionkey=%s", arg1, arg2, sessionKey);
}
}
/*
* Copyright (C) 2012 THM webMedia
*
* This file is part of ARSnova.
*
* ARSnova is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.scribe.up.provider.impl.FacebookProvider;
import org.scribe.up.provider.impl.Google2Provider;
import org.scribe.up.provider.impl.TwitterProvider;
import org.scribe.up.session.HttpUserSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.cas.authentication.CasAuthenticationToken;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.token.Sha512DigestUtils;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.RedirectView;
import de.thm.arsnova.entities.ServiceDescription;
import de.thm.arsnova.entities.Session;
import de.thm.arsnova.entities.User;
import de.thm.arsnova.exceptions.UnauthorizedException;
import de.thm.arsnova.services.IUserService;
import de.thm.arsnova.services.UserSessionService;
@Controller
public class LoginController extends AbstractController {
private static final int MAX_USERNAME_LENGTH = 15;
private static final int MAX_GUESTHASH_LENGTH = 10;
@Value("${customization.path}") private String customizationPath;
@Value("${security.guest.enabled}") private String guestEnabled;
@Value("${security.guest.lecturer.enabled}") private String guestLecturerEnabled;
@Value("${security.guest.order}") private int guestOrder;
@Value("${security.custom-login.enabled}") private String customLoginEnabled;
@Value("${security.custom-login.title:University}") private String customLoginTitle;
@Value("${security.custom-login.login-dialog-path}") private String customLoginDialog;
@Value("${security.custom-login.image:}") private String customLoginImage;
@Value("${security.custom-login.order}") private int customLoginOrder;
@Value("${security.user-db.enabled}") private String dbAuthEnabled;
@Value("${security.user-db.title:ARSnova}") private String dbAuthTitle;
@Value("${security.user-db.login-dialog-path}") private String dbAuthDialog;
@Value("${security.user-db.image:}") private String dbAuthImage;
@Value("${security.user-db.order}") private int dbAuthOrder;
@Value("${security.ldap.enabled}") private String ldapEnabled;
@Value("${security.ldap.title:LDAP}") private String ldapTitle;
@Value("${security.ldap.login-dialog-path}") private String ldapDialog;
@Value("${security.ldap.image:}") private String ldapImage;
@Value("${security.ldap.order}") private int ldapOrder;
@Value("${security.cas.enabled}") private String casEnabled;
@Value("${security.cas.title:CAS}") private String casTitle;
@Value("${security.cas.image:}") private String casImage;
@Value("${security.cas.order}") private int casOrder;
@Value("${security.facebook.enabled}") private String facebookEnabled;
@Value("${security.facebook.order}") private int facebookOrder;
@Value("${security.google.enabled}") private String googleEnabled;
@Value("${security.google.order}") private int googleOrder;
@Value("${security.twitter.enabled}") private String twitterEnabled;
@Value("${security.twitter.order}") private int twitterOrder;
@Autowired(required = false)
private DaoAuthenticationProvider daoProvider;
@Autowired(required = false)
private TwitterProvider twitterProvider;
@Autowired(required = false)
private Google2Provider googleProvider;
@Autowired(required = false)
private FacebookProvider facebookProvider;
@Autowired(required = false)
private LdapAuthenticationProvider ldapAuthenticationProvider;
@Autowired(required = false)
private CasAuthenticationEntryPoint casEntryPoint;
@Autowired
private IUserService userService;
@Autowired
private UserSessionService userSessionService;
public static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class);
@RequestMapping(value = { "/auth/login", "/doLogin" }, method = { RequestMethod.POST, RequestMethod.GET })
public final void doLogin(
@RequestParam("type") final String type,
@RequestParam(value = "user", required = false) String username,
@RequestParam(required = false) final String password,
@RequestParam(value = "role", required = false) final UserSessionService.Role role,
final HttpServletRequest request,
final HttpServletResponse response
) throws IOException {
String addr = request.getRemoteAddr();
if (userService.isBannedFromLogin(addr)) {
response.sendError(429, "Too Many Requests");
return;
}
userSessionService.setRole(role);
if ("arsnova".equals(type)) {
Authentication authRequest = new UsernamePasswordAuthenticationToken(username, password);
try {
Authentication auth = daoProvider.authenticate(authRequest);
if (auth.isAuthenticated()) {
SecurityContextHolder.getContext().setAuthentication(auth);
request.getSession(true).setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
SecurityContextHolder.getContext());
return;
}
} catch (AuthenticationException e) {
LOGGER.info("Authentication failed: {}", e.getMessage());
}
userService.increaseFailedLoginCount(addr);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
} else if ("ldap".equals(type)) {
if (!"".equals(username) && !"".equals(password)) {
org.springframework.security.core.userdetails.User user =
new org.springframework.security.core.userdetails.User(
username, password, true, true, true, true, this.getAuthorities()
);
Authentication token = new UsernamePasswordAuthenticationToken(user, password, getAuthorities());
try {
Authentication auth = ldapAuthenticationProvider.authenticate(token);
if (auth.isAuthenticated()) {
SecurityContextHolder.getContext().setAuthentication(token);
request.getSession(true).setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
SecurityContextHolder.getContext());
return;
}
LOGGER.info("LDAPLOGIN: {}", auth.isAuthenticated());
} catch (AuthenticationException e) {
LOGGER.info("No LDAP login: {}", e);
}
userService.increaseFailedLoginCount(addr);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
}
} else if ("guest".equals(type)) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_GUEST"));
if (username == null || !username.startsWith("Guest") || username.length() != MAX_USERNAME_LENGTH) {
username = "Guest" + Sha512DigestUtils.shaHex(request.getSession().getId()).substring(0, MAX_GUESTHASH_LENGTH);
}
org.springframework.security.core.userdetails.User user =
new org.springframework.security.core.userdetails.User(
username, "", true, true, true, true, authorities
);
Authentication token = new UsernamePasswordAuthenticationToken(user, null, authorities);
SecurityContextHolder.getContext().setAuthentication(token);
request.getSession(true).setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
SecurityContextHolder.getContext());
}
}
@RequestMapping(value = { "/auth/dialog" }, method = RequestMethod.GET)
@ResponseBody
public final View dialog(
@RequestParam("type") final String type,
@RequestParam(value = "successurl", defaultValue = "/") String successUrl,
@RequestParam(value = "failureurl", defaultValue = "/") String failureUrl,
final HttpServletRequest request,
final HttpServletResponse response
) throws IOException, ServletException {
View result = null;
/* Use URLs from a request parameters for redirection as long as the
* URL is not absolute (to prevent abuse of the redirection). */
if (UrlUtils.isAbsoluteUrl(successUrl)) {
successUrl = "/";
}
if (UrlUtils.isAbsoluteUrl(failureUrl)) {
failureUrl = "/";
}
/* Workaround until a solution is found to do a redirect which is
* relative to the server root instead of the context path */
String port;
if ("https".equals(request.getScheme())) {
port = 443 != request.getServerPort() ? ":" + request.getLocalPort() : "";
} else {
port = 80 != request.getServerPort() ? ":" + request.getLocalPort() : "";
}
String serverUrl = request.getScheme() + "://" + request.getServerName() + port;
request.getSession().setAttribute("ars-login-success-url", serverUrl + successUrl);
request.getSession().setAttribute("ars-login-failure-url", serverUrl + failureUrl);
if ("cas".equals(type)) {
casEntryPoint.commence(request, response, null);
} else if ("twitter".equals(type)) {
final String authUrl = twitterProvider.getAuthorizationUrl(new HttpUserSession(request));
result = new RedirectView(authUrl);
} else if ("facebook".equals(type)) {
facebookProvider.setFields("id,link");
facebookProvider.setScope("");
final String authUrl = facebookProvider.getAuthorizationUrl(new HttpUserSession(request));
result = new RedirectView(authUrl);
} else if ("google".equals(type)) {
final String authUrl = googleProvider.getAuthorizationUrl(new HttpUserSession(request));
result = new RedirectView(authUrl);
}
return result;
}
@RequestMapping(value = { "/auth/", "/whoami" }, method = RequestMethod.GET)
@ResponseBody
public final User whoami() {
userSessionService.setUser(userService.getCurrentUser());
return userService.getCurrentUser();
}
@RequestMapping(value = { "/auth/logout", "/logout" }, method = { RequestMethod.POST, RequestMethod.GET })
public final View doLogout(final HttpServletRequest request) {
final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
userService.removeUserFromMaps(userService.getCurrentUser());
request.getSession().invalidate();
SecurityContextHolder.clearContext();
if (auth instanceof CasAuthenticationToken) {
return new RedirectView("/j_spring_cas_security_logout", true);
}
return new RedirectView(request.getHeader("referer") != null ? request.getHeader("referer") : "/");
}
@RequestMapping(value = { "/auth/services" }, method = RequestMethod.GET)
@ResponseBody
public final List<ServiceDescription> getServices(final HttpServletRequest request) {
List<ServiceDescription> services = new ArrayList<ServiceDescription>();
/* The first parameter is replaced by the backend, the second one by the frondend */
String dialogUrl = request.getContextPath() + "/auth/dialog?type={0}&successurl='{0}'";
if ("true".equals(guestEnabled)) {
ServiceDescription sdesc = new ServiceDescription(
"guest",
"Guest",
null
);
sdesc.setOrder(guestOrder);
if (!"true".equals(guestLecturerEnabled)) {
sdesc.setAllowLecturer(false);
}
services.add(sdesc);
}
if ("true".equals(customLoginEnabled) && !"".equals(customLoginDialog)) {
ServiceDescription sdesc = new ServiceDescription(
"custom",
customLoginTitle,
customizationPath + "/" + customLoginDialog + "?redirect={0}",
customLoginImage
);
sdesc.setOrder(customLoginOrder);
services.add(sdesc);
}
if ("true".equals(dbAuthEnabled) && !"".equals(dbAuthDialog)) {
ServiceDescription sdesc = new ServiceDescription(
"arsnova",
dbAuthTitle,
customizationPath + "/" + dbAuthDialog + "?redirect={0}",
dbAuthImage
);
sdesc.setOrder(dbAuthOrder);
services.add(sdesc);
}
if ("true".equals(ldapEnabled) && !"".equals(ldapDialog)) {
ServiceDescription sdesc = new ServiceDescription(
"ldap",
ldapTitle,
customizationPath + "/" + ldapDialog + "?redirect={0}",
ldapImage
);
sdesc.setOrder(ldapOrder);
services.add(sdesc);
}
if ("true".equals(casEnabled)) {
ServiceDescription sdesc = new ServiceDescription(
"cas",
casTitle,
MessageFormat.format(dialogUrl, "cas")
);
sdesc.setOrder(casOrder);
services.add(sdesc);
}
if ("true".equals(facebookEnabled)) {
ServiceDescription sdesc = new ServiceDescription(
"facebook",
"Facebook",
MessageFormat.format(dialogUrl, "facebook")
);
sdesc.setOrder(facebookOrder);
services.add(sdesc);
}
if ("true".equals(googleEnabled)) {
ServiceDescription sdesc = new ServiceDescription(
"google",
"Google",
MessageFormat.format(dialogUrl, "google")
);
sdesc.setOrder(googleOrder);
services.add(sdesc);
}
if ("true".equals(twitterEnabled)) {
ServiceDescription sdesc = new ServiceDescription(
"twitter",
"Twitter",
MessageFormat.format(dialogUrl, "twitter")
);
sdesc.setOrder(twitterOrder);
services.add(sdesc);
}
return services;
}
private Collection<GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
authList.add(new SimpleGrantedAuthority("ROLE_USER"));
return authList;
}
@RequestMapping(value = { "/test/me" }, method = RequestMethod.GET)
@ResponseBody
public final User me() {
final User me = userSessionService.getUser();
if (me == null) {
throw new UnauthorizedException();
}
return me;
}
@RequestMapping(value = { "/test/mysession" }, method = RequestMethod.GET)
@ResponseBody
public final Session mysession() {
final Session mysession = userSessionService.getSession();
if (mysession == null) {
throw new UnauthorizedException();
}
return mysession;
}
@RequestMapping(value = { "/test/myrole" }, method = RequestMethod.GET)
@ResponseBody
public final UserSessionService.Role myrole() {
final UserSessionService.Role myrole = userSessionService.getRole();
if (myrole == null) {
throw new UnauthorizedException();
}
return myrole;
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import de.thm.arsnova.model.Motd;
import de.thm.arsnova.service.MotdService;
@RestController
@RequestMapping(MotdController.REQUEST_MAPPING)
public class MotdController extends AbstractEntityController<Motd> {
protected static final String REQUEST_MAPPING = "/motd";
private MotdService motdService;
public MotdController(final MotdService motdService) {
super(motdService);
this.motdService = motdService;
}
@Override
protected String getMapping() {
return REQUEST_MAPPING;
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller;
/**
* Adds pagination properties to controllers.
*/
public abstract class PaginationController extends AbstractController {
protected int offset = -1;
protected int limit = -1;
public void setRange(final int start, final int end) {
this.offset = start;
this.limit = end != -1 && start <= end ? end - start + 1 : -1;
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import de.thm.arsnova.model.ContentGroup;
import de.thm.arsnova.model.Room;
import de.thm.arsnova.model.RoomStatistics;
import de.thm.arsnova.service.ContentGroupService;
import de.thm.arsnova.service.RoomService;
@RestController
@RequestMapping(RoomController.REQUEST_MAPPING)
public class RoomController extends AbstractEntityController<Room> {
protected static final String REQUEST_MAPPING = "/room";
private static final String GET_MODERATORS_MAPPING = DEFAULT_ID_MAPPING + "/moderator";
private static final String MODERATOR_MAPPING = DEFAULT_ID_MAPPING + "/moderator/{userId}";
private static final String CONTENTGROUP_MAPPING = DEFAULT_ID_MAPPING + "/contentgroup/{groupName}";
private static final String CONTENTGROUP_ADD_MAPPING = CONTENTGROUP_MAPPING + "/{contentId}";
private static final String STATS_MAPPING = DEFAULT_ID_MAPPING + "/stats";
private RoomService roomService;
private ContentGroupService contentGroupService;
public RoomController(final RoomService roomService, final ContentGroupService contentGroupService) {
super(roomService);
this.roomService = roomService;
this.contentGroupService = contentGroupService;
}
@Override
protected String getMapping() {
return REQUEST_MAPPING;
}
@Override
protected String resolveAlias(final String shortId) {
return roomService.getIdByShortId(shortId);
}
@GetMapping(GET_MODERATORS_MAPPING)
public Set<Room.Moderator> getModerators(@PathVariable final String id) {
return roomService.get(id).getModerators();
}
@PutMapping(MODERATOR_MAPPING)
public void putModerator(@PathVariable final String id, @PathVariable final String userId,
@RequestBody final Room.Moderator moderator, final HttpServletResponse httpServletResponse) {
final Room room = roomService.get(id);
moderator.setUserId(userId);
if (moderator.getRoles().isEmpty()) {
moderator.getRoles().add(Room.Moderator.Role.EXECUTIVE_MODERATOR);
}
room.getModerators().removeIf(m -> m.getUserId().equals(userId));
room.getModerators().add(moderator);
roomService.update(room);
httpServletResponse.setHeader(ENTITY_ID_HEADER, room.getId());
httpServletResponse.setHeader(ENTITY_REVISION_HEADER, room.getRevision());
}
@DeleteMapping(MODERATOR_MAPPING)
public void deleteModerator(@PathVariable final String id, @PathVariable final String userId,
final HttpServletResponse httpServletResponse) {
final Room room = roomService.get(id);
room.getModerators().removeIf(m -> m.getUserId().equals(userId));
roomService.update(room);
httpServletResponse.setHeader(ENTITY_ID_HEADER, room.getId());
httpServletResponse.setHeader(ENTITY_REVISION_HEADER, room.getRevision());
}
@GetMapping(CONTENTGROUP_MAPPING)
public ContentGroup getContentGroup(@PathVariable final String id, @PathVariable final String groupName) {
return contentGroupService.getByRoomIdAndName(id, groupName);
}
@PostMapping(CONTENTGROUP_ADD_MAPPING)
public void addContentToGroup(@PathVariable final String id, @PathVariable final String groupName,
@RequestBody final String contentId) {
contentGroupService.addContentToGroup(id, groupName, contentId);
}
@PutMapping(CONTENTGROUP_MAPPING)
public void updateGroup(@PathVariable final String id, @PathVariable final String groupName,
@RequestBody final ContentGroup contentGroup) {
contentGroupService.updateContentGroup(contentGroup);
}
@GetMapping(STATS_MAPPING)
public RoomStatistics getStats(@PathVariable final String id) {
final RoomStatistics roomStatistics = new RoomStatistics();
final List<ContentGroup> contentGroups = contentGroupService.getByRoomId(id);
roomStatistics.setGroupStats(contentGroups.stream()
.map(cg -> new RoomStatistics.ContentGroupStatistics(cg)).collect(Collectors.toList()));
roomStatistics.setContentCount(contentGroups.stream()
.mapToInt(cg -> cg.getContentIds().size()).reduce((a, b) -> a + b).orElse(0));
return roomStatistics;
}
}
package de.thm.arsnova.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import de.thm.arsnova.exceptions.BadRequestException;
import de.thm.arsnova.exceptions.ForbiddenException;
import de.thm.arsnova.exceptions.NoContentException;
import de.thm.arsnova.exceptions.NotFoundException;
import de.thm.arsnova.exceptions.NotImplementedException;
import de.thm.arsnova.exceptions.PreconditionFailedException;
import de.thm.arsnova.exceptions.UnauthorizedException;
@ControllerAdvice
public class SecurityExceptionControllerAdvice {
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(NotFoundException.class)
public void handleNotFoundException(final Exception e, final HttpServletRequest request) {
}
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(UnauthorizedException.class)
public void handleUnauthorizedException(final Exception e, final HttpServletRequest request) {
}
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(AuthenticationCredentialsNotFoundException.class)
public void handleAuthenticationCredentialsNotFoundException(final Exception e, final HttpServletRequest request) {
}
@ExceptionHandler(AccessDeniedException.class)
public void handleAccessDeniedException(
final Exception e,
final HttpServletRequest request,
final HttpServletResponse response
) {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (
authentication == null
|| authentication.getPrincipal() == null
|| authentication instanceof AnonymousAuthenticationToken
) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
return;
}
response.setStatus(HttpStatus.FORBIDDEN.value());
}
@ResponseStatus(HttpStatus.FORBIDDEN)
@ExceptionHandler(ForbiddenException.class)
public void handleForbiddenException(final Exception e, final HttpServletRequest request) {
}
@ResponseStatus(HttpStatus.NO_CONTENT)
@ExceptionHandler(NoContentException.class)
public void handleNoContentException(final Exception e, final HttpServletRequest request) {
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(BadRequestException.class)
public void handleBadRequestException(final Exception e, final HttpServletRequest request) {
}
@ResponseStatus(HttpStatus.PRECONDITION_FAILED)
@ExceptionHandler(PreconditionFailedException.class)
public void handlePreconditionFailedException(final Exception e, final HttpServletRequest request) {
}
@ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
@ExceptionHandler(NotImplementedException.class)
public void handleNotImplementedException(final Exception e, final HttpServletRequest request) {
}
}
/*
* Copyright (C) 2012 THM webMedia
*
* This file is part of ARSnova.
*
* ARSnova is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import net.sf.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.token.Sha512DigestUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import de.thm.arsnova.connector.model.Course;
import de.thm.arsnova.entities.Session;
import de.thm.arsnova.entities.SessionInfo;
import de.thm.arsnova.exceptions.UnauthorizedException;
import de.thm.arsnova.services.ISessionService;
import de.thm.arsnova.services.IUserService;
import de.thm.arsnova.services.SessionService.SessionNameComparator;
import de.thm.arsnova.services.SessionService.SessionInfoNameComparator;
import de.thm.arsnova.services.SessionService.SessionShortNameComparator;
import de.thm.arsnova.services.SessionService.SessionInfoShortNameComparator;
import de.thm.arsnova.web.DeprecatedApi;
@RestController
@RequestMapping("/session")
public class SessionController extends AbstractController {
public static final Logger LOGGER = LoggerFactory.getLogger(SessionController.class);
@Autowired
private ISessionService sessionService;
@Autowired
private IUserService userService;
@RequestMapping(value = "/{sessionkey}", method = RequestMethod.GET)
public final Session joinSession(@PathVariable final String sessionkey) {
final Session session = sessionService.getSession(sessionkey);
if (!session.isCreator(userService.getCurrentUser())) {
session.setCreator("NOT VISIBLE TO YOU");
} else {
session.setCreator(Sha512DigestUtils.shaHex(session.getCreator()));
}
return session;
}
@RequestMapping(value = "/{sessionkey}", method = RequestMethod.DELETE)
public final void deleteSession(@PathVariable final String sessionkey) {
sessionService.deleteSession(sessionkey);
}
@DeprecatedApi
@RequestMapping(value = "/{sessionkey}/activeusercount", method = RequestMethod.GET)
public final int countActiveUsers(@PathVariable final String sessionkey) {
return sessionService.activeUsers(sessionkey);
}
@RequestMapping(value = "/", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
public final Session postNewSession(@RequestBody final Session session, final HttpServletResponse response) {
if (session != null && session.isCourseSession()) {
final List<Course> courses = new ArrayList<Course>();
final Course course = new Course();
course.setId(session.getCourseId());
courses.add(course);
final int sessionCount = sessionService.countSessions(courses);
if (sessionCount > 0) {
final String appendix = " (" + (sessionCount + 1) + ")";
session.setName(session.getName() + appendix);
session.setShortName(session.getShortName() + appendix);
}
}
final Session newSession = sessionService.saveSession(session);
if (newSession == null) {
response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value());
return null;
}
return newSession;
}
@RequestMapping(value = "/{sessionkey}", method = RequestMethod.PUT)
public final Session updateSession(
@PathVariable final String sessionkey,
@RequestBody final Session session
) {
return sessionService.updateSession(sessionkey, session);
}
@RequestMapping(value = "/", method = RequestMethod.GET)
public final List<Session> getSessions(
@RequestParam(value = "ownedonly", defaultValue = "false") final boolean ownedOnly,
@RequestParam(value = "visitedonly", defaultValue = "false") final boolean visitedOnly,
@RequestParam(value = "sortby", defaultValue = "name") final String sortby,
final HttpServletResponse response
) {
List<Session> sessions = null;
/* TODO implement all parameter combinations, implement use of user parameter */
try {
if (ownedOnly && !visitedOnly) {
sessions = sessionService.getMySessions();
} else if (visitedOnly && !ownedOnly) {
sessions = sessionService.getMyVisitedSessions();
} else {
response.setStatus(HttpStatus.NOT_IMPLEMENTED.value());
return null;
}
} catch (final AccessDeniedException e) {
throw new UnauthorizedException();
}
if (sessions == null || sessions.isEmpty()) {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
return null;
}
if (sortby != null && sortby.equals("shortname")) {
Collections.sort(sessions, new SessionShortNameComparator());
} else {
Collections.sort(sessions, new SessionNameComparator());
}
return sessions;
}
/**
* Returns a list of my own sessions with only the necessary information like name, keyword, or counters.
* @param statusOnly The flag that has to be set in order to get this shortened list.
* @param response
* @return
*/
@RequestMapping(value = "/", method = RequestMethod.GET, params = "statusonly=true")
public final List<SessionInfo> getMySessions(
@RequestParam(value = "visitedonly", defaultValue = "false") final boolean visitedOnly,
@RequestParam(value = "sortby", defaultValue = "name") final String sortby,
final HttpServletResponse response
) {
List<SessionInfo> sessions;
if (!visitedOnly) {
sessions = sessionService.getMySessionsInfo();
} else {
sessions = sessionService.getMyVisitedSessionsInfo();
}
if (sessions == null || sessions.isEmpty()) {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
return null;
}
if (sortby != null && sortby.equals("shortname")) {
Collections.sort(sessions, new SessionInfoShortNameComparator());
} else {
Collections.sort(sessions, new SessionInfoNameComparator());
}
return sessions;
}
@RequestMapping(value = "/{sessionkey}/lock", method = RequestMethod.POST)
public final Session lockSession(
@PathVariable final String sessionkey,
@RequestParam(required = false) final Boolean lock,
final HttpServletResponse response
) {
if (lock != null) {
return sessionService.setActive(sessionkey, lock);
}
response.setStatus(HttpStatus.NOT_FOUND.value());
return null;
}
@RequestMapping(value = "/{sessionkey}/learningprogress", method = RequestMethod.GET)
public final int learningProgress(
@PathVariable final String sessionkey,
final HttpServletResponse response
) {
return sessionService.getLearningProgress(sessionkey);
}
@RequestMapping(value = "/{sessionkey}/mylearningprogress", method = RequestMethod.GET)
public final JSONObject myLearningProgress(
@PathVariable final String sessionkey,
final HttpServletResponse response
) {
final SimpleEntry<Integer, Integer> result = sessionService.getMyLearningProgress(sessionkey);
final JSONObject json = new JSONObject();
json.put("myprogress", result.getKey());
json.put("courseprogress", result.getValue());
return json;
}
/* internal redirections */
@RequestMapping(value = "/{sessionKey}/lecturerquestion")
public final String redirectLecturerQuestion(
@PathVariable final String sessionKey,
final HttpServletResponse response
) {
response.addHeader(X_FORWARDED, "1");
return String.format("forward:/lecturerquestion/?sessionkey=%s", sessionKey);
}
@RequestMapping(value = "/{sessionKey}/lecturerquestion/{arg1}")
public final String redirectLecturerQuestionWithOneArgument(
@PathVariable final String sessionKey,
@PathVariable final String arg1,
final HttpServletResponse response
) {
response.addHeader(X_FORWARDED, "1");
return String.format("forward:/lecturerquestion/%s/?sessionkey=%s", arg1, sessionKey);
}
@RequestMapping(value = "/{sessionKey}/lecturerquestion/{arg1}/{arg2}")
public final String redirectLecturerQuestionWithTwoArguments(
@PathVariable final String sessionKey,
@PathVariable final String arg1,
@PathVariable final String arg2,
final HttpServletResponse response
) {
response.addHeader(X_FORWARDED, "1");
return String.format("forward:/lecturerquestion/%s/%s/?sessionkey=%s", arg1, arg2, sessionKey);
}
@RequestMapping(value = "/{sessionKey}/lecturerquestion/{arg1}/{arg2}/{arg3}")
public final String redirectLecturerQuestionWithThreeArguments(
@PathVariable final String sessionKey,
@PathVariable final String arg1,
@PathVariable final String arg2,
@PathVariable final String arg3,
final HttpServletResponse response
) {
response.addHeader(X_FORWARDED, "1");
return String.format("forward:/lecturerquestion/%s/%s/%s/?sessionkey=%s", arg1, arg2, arg3, sessionKey);
}
@RequestMapping(value = "/{sessionKey}/audiencequestion")
public final String redirectAudienceQuestion(
@PathVariable final String sessionKey,
final HttpServletResponse response
) {
response.addHeader(X_FORWARDED, "1");
return String.format("forward:/audiencequestion/?sessionkey=%s", sessionKey);
}
@RequestMapping(value = "/{sessionKey}/audiencequestion/{arg1}")
public final String redirectAudienceQuestionWithOneArgument(
@PathVariable final String sessionKey,
@PathVariable final String arg1,
final HttpServletResponse response
) {
response.addHeader(X_FORWARDED, "1");
return String.format("forward:/audiencequestion/%s/?sessionkey=%s", arg1, sessionKey);
}
@RequestMapping(value = "/{sessionKey}/audiencequestion/{arg1}/{arg2}")
public final String redirectAudienceQuestionWithTwoArguments(
@PathVariable final String sessionKey,
@PathVariable final String arg1,
@PathVariable final String arg2,
final HttpServletResponse response
) {
response.addHeader(X_FORWARDED, "1");
return String.format("forward:/audiencequestion/%s/%s/?sessionkey=%s", arg1, arg2, sessionKey);
}
@RequestMapping(value = "/{sessionKey}/audiencequestion/{arg1}/{arg2}/{arg3}")
public final String redirectAudienceQuestionWithThreeArguments(
@PathVariable final String sessionKey,
@PathVariable final String arg1,
@PathVariable final String arg2,
@PathVariable final String arg3,
final HttpServletResponse response
) {
response.addHeader(X_FORWARDED, "1");
return String.format("forward:/audiencequestion/%s/%s/%s/?sessionkey=%s", arg1, arg2, arg3, sessionKey);
}
}
package de.thm.arsnova.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import de.thm.arsnova.entities.Statistics;
import de.thm.arsnova.services.IStatisticsService;
import de.thm.arsnova.web.CacheControl;
import de.thm.arsnova.web.DeprecatedApi;
@RestController
public class StatisticsController extends AbstractController {
@Autowired
private IStatisticsService statisticsService;
@RequestMapping(method = RequestMethod.GET, value = "/statistics")
@CacheControl(maxAge = 60, policy = CacheControl.Policy.PUBLIC)
public final Statistics getStatistics() {
return statisticsService.getStatistics();
}
@DeprecatedApi
@RequestMapping(method = RequestMethod.GET, value = "/statistics/activeusercount", produces = "text/plain")
public final String countActiveUsers() {
return Integer.toString(statisticsService.getStatistics().getActiveUsers());
}
@DeprecatedApi
@RequestMapping(method = RequestMethod.GET, value = "/statistics/loggedinusercount", produces = "text/plain")
public final String countLoggedInUsers() {
return Integer.toString(statisticsService.getStatistics().getLoggedinUsers());
}
@DeprecatedApi
@RequestMapping(method = RequestMethod.GET, value = "/statistics/sessioncount", produces = "text/plain")
public final String countSessions() {
return Integer.toString(statisticsService.getStatistics().getOpenSessions()
+ statisticsService.getStatistics().getClosedSessions());
}
}
/*
* Copyright (C) 2012 THM webMedia
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* This file is part of ARSnova.
*
* ARSnova is free software: you can redistribute it and/or modify
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova is distributed in the hope that it will be useful,
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
......@@ -16,115 +15,128 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller;
import com.fasterxml.jackson.annotation.JsonView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import de.thm.arsnova.model.LoginCredentials;
import de.thm.arsnova.model.UserProfile;
import de.thm.arsnova.model.serialization.View;
import de.thm.arsnova.service.RoomService;
import de.thm.arsnova.service.UserService;
import de.thm.arsnova.web.exceptions.BadRequestException;
import de.thm.arsnova.web.exceptions.ForbiddenException;
@RestController
@RequestMapping(UserController.REQUEST_MAPPING)
public class UserController extends AbstractEntityController<UserProfile> {
protected static final String REQUEST_MAPPING = "/user";
private static final String REGISTER_MAPPING = "/register";
private static final String ACTIVATE_MAPPING = DEFAULT_ID_MAPPING + "/activate";
private static final String RESET_PASSWORD_MAPPING = DEFAULT_ID_MAPPING + "/resetpassword";
private static final String ROOM_HISTORY_MAPPING = DEFAULT_ID_MAPPING + "/roomHistory";
private UserService userService;
private RoomService roomService;
public UserController(final UserService userService, final RoomService roomService) {
super(userService);
this.userService = userService;
this.roomService = roomService;
}
import de.thm.arsnova.entities.DbUser;
import de.thm.arsnova.services.IUserService;
import de.thm.arsnova.services.UserSessionService;
@Controller
@RequestMapping("/user")
public class UserController extends AbstractController {
@Value("${security.guest.enabled}")
private String guestEnabled;
@Value("${security.guest.lecturer.enabled}")
private String guestLecturerEnabled;
@Value("${security.cas.enabled}")
private String casEnabled;
@Value("${security.ldap.enabled}")
private String ldapEnabled;
@Value("${security.facebook.enabled}")
private String facebookEnabled;
@Value("${security.google.enabled}")
private String googleEnabled;
@Value("${security.twitter.enabled}")
private String twitterEnabled;
@Autowired
private DaoAuthenticationProvider daoProvider;
@Autowired
private IUserService userService;
@Autowired
private UserSessionService userSessionService;
public static final Logger LOGGER = LoggerFactory
.getLogger(UserController.class);
@RequestMapping(value = { "/register" }, method = RequestMethod.POST)
public final void register(@RequestParam final String username,
@RequestParam final String password,
final HttpServletRequest request, final HttpServletResponse response) {
if (null != userService.createDbUser(username, password)) {
return;
class Activation {
private String key;
public String getKey() {
return key;
}
/* TODO: Improve error handling: send reason to client */
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
@JsonView(View.Public.class)
public void setKey(final String key) {
this.key = key;
}
}
@RequestMapping(value = { "/{username}/activate" }, method = { RequestMethod.POST,
RequestMethod.GET })
public final void activate(
@PathVariable final String username,
@RequestParam final String key, final HttpServletRequest request,
final HttpServletResponse response) {
DbUser dbUser = userService.getDbUser(username);
if (null != dbUser && key.equals(dbUser.getActivationKey())) {
dbUser.setActivationKey(null);
userService.updateDbUser(dbUser);
return;
class PasswordReset {
private String key;
private String password;
public String getKey() {
return key;
}
@JsonView(View.Public.class)
public void setKey(final String key) {
this.key = key;
}
public String getPassword() {
return password;
}
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
@JsonView(View.Public.class)
public void setPassword(final String password) {
this.password = password;
}
}
@Override
protected String getMapping() {
return REQUEST_MAPPING;
}
@PostMapping(REGISTER_MAPPING)
public void register(@RequestBody final LoginCredentials loginCredentials) {
if (userService.create(loginCredentials.getLoginId(), loginCredentials.getPassword()) == null) {
throw new ForbiddenException();
}
}
@RequestMapping(value = { "/{username}" }, method = RequestMethod.DELETE)
public final void activate(
@PathVariable final String username,
final HttpServletRequest request,
final HttpServletResponse response) {
if (null == userService.deleteDbUser(username)) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
@PostMapping(ACTIVATE_MAPPING)
public void activate(
@PathVariable final String id,
@RequestParam final String key,
final HttpServletRequest request) {
if (!userService.activateAccount(id, key, request.getRemoteAddr())) {
throw new BadRequestException();
}
}
@RequestMapping(value = { "/{username}/resetpassword" }, method = RequestMethod.POST)
public final void resetPassword(
@PathVariable final String username,
@RequestParam(required = false) final String key,
@RequestParam(required = false) final String password,
final HttpServletRequest request,
final HttpServletResponse response) {
DbUser dbUser = userService.getDbUser(username);
if (null == dbUser) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
@PostMapping(RESET_PASSWORD_MAPPING)
public void resetPassword(
@PathVariable final String id,
@RequestBody final PasswordReset passwordReset) {
final UserProfile userProfile = userService.get(id, true);
if (userProfile == null) {
throw new BadRequestException();
}
if (null != key) {
if (!userService.resetPassword(dbUser, key, password)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
if (passwordReset.getKey() != null) {
if (!userService.resetPassword(userProfile, passwordReset.getKey(), passwordReset.getPassword())) {
throw new ForbiddenException();
}
} else {
userService.initiatePasswordReset(username);
userService.initiatePasswordReset(id);
}
}
@PostMapping(ROOM_HISTORY_MAPPING)
public void postRoomHistoryEntry(@PathVariable final String id,
@RequestBody final UserProfile.RoomHistoryEntry roomHistoryEntry) {
userService.addRoomToHistory(userService.get(id), roomService.get(roomHistoryEntry.getRoomId()));
}
@Override
protected String resolveAlias(final String alias) {
return userService.getByUsername(alias).getId();
}
}
/*
* Copyright (C) 2012 THM webMedia
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* This file is part of ARSnova.
*
* ARSnova is free software: you can redistribute it and/or modify
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova is distributed in the hope that it will be useful,
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
......@@ -16,35 +15,89 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller;
import java.util.HashMap;
package de.thm.arsnova.controller;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.RedirectView;
import de.thm.arsnova.management.VersionInfoContributor;
import de.thm.arsnova.web.exceptions.BadRequestException;
import de.thm.arsnova.web.exceptions.NoContentException;
/**
* Default controller that handles requests which have not set a path.
*/
@Controller
public class WelcomeController extends AbstractController {
@Value("${mobile.path}")
private String mobileContextPath;
@RequestMapping(value = "/", method = RequestMethod.GET)
public final View home(final HttpServletRequest request) {
@Autowired
private VersionInfoContributor versionInfoContributor;
@GetMapping("/")
public View home() {
return new RedirectView(mobileContextPath + "/", false);
}
@RequestMapping(value = "/", method = RequestMethod.GET, produces = "application/json")
@GetMapping(value = "/", produces = "application/json")
@ResponseBody
public final HashMap<String, Object> jsonHome(final HttpServletRequest request) {
return new HashMap<String, Object>();
public Map<String, Object> jsonHome() {
return versionInfoContributor.getInfoDetails();
}
@PostMapping("/checkframeoptionsheader")
@ResponseStatus(HttpStatus.OK)
public void checkFrameOptionsHeader(
@RequestParam final String url,
final HttpServletRequest request) {
/* Block requests from the server itself to prevent DoS attacks caused by request loops */
if ("127.0.0.1".equals(request.getRemoteAddr()) || "::1".equals(request.getRemoteAddr())) {
throw new BadRequestException("Access to localhost not allowed.");
}
/* Block requests to servers in private networks */
try {
final InetAddress addr = InetAddress.getByName(new URL(url).getHost());
if (addr.isSiteLocalAddress()) {
throw new BadRequestException("Access to site-local addresses not allowed.");
}
} catch (final UnknownHostException | MalformedURLException e) {
throw new BadRequestException();
}
final RestTemplate restTemplate = new RestTemplate();
final SimpleClientHttpRequestFactory rf = (SimpleClientHttpRequestFactory) restTemplate.getRequestFactory();
rf.setConnectTimeout(2000);
rf.setReadTimeout(2000);
try {
final HttpHeaders headers = restTemplate.headForHeaders(url);
if (headers.isEmpty() || headers.containsKey("x-frame-options")) {
throw new NoContentException();
}
} catch (final RestClientException e) {
throw new NoContentException();
}
}
}
/**
* Controllers and helper classes which define the public API.
*/
package de.thm.arsnova.controller;
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller.v2;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.pac4j.core.context.J2EContext;
import org.pac4j.core.exception.HttpAction;
import org.pac4j.oauth.client.FacebookClient;
import org.pac4j.oauth.client.TwitterClient;
import org.pac4j.oidc.client.GoogleOidcClient;
import org.pac4j.oidc.client.OidcClient;
import org.pac4j.saml.client.SAML2Client;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.cas.authentication.CasAuthenticationToken;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.RedirectView;
import de.thm.arsnova.config.SecurityConfig;
import de.thm.arsnova.config.properties.AuthenticationProviderProperties;
import de.thm.arsnova.config.properties.SystemProperties;
import de.thm.arsnova.controller.AbstractController;
import de.thm.arsnova.model.ServiceDescription;
import de.thm.arsnova.model.UserProfile;
import de.thm.arsnova.model.migration.v2.ClientAuthentication;
import de.thm.arsnova.security.LoginAuthenticationFailureHandler;
import de.thm.arsnova.security.LoginAuthenticationSucessHandler;
import de.thm.arsnova.security.User;
import de.thm.arsnova.service.UserService;
import de.thm.arsnova.web.exceptions.UnauthorizedException;
/**
* Handles authentication specific requests.
*/
@Controller("v2AuthenticationController")
@RequestMapping("/v2/auth")
public class AuthenticationController extends AbstractController {
private String apiPath;
@Value("${customization.path}") private String customizationPath;
private AuthenticationProviderProperties.Guest guestProperties;
private AuthenticationProviderProperties.Registered registeredProperties;
private List<AuthenticationProviderProperties.Ldap> ldapProperties;
private List<AuthenticationProviderProperties.Oidc> oidcProperties;
private AuthenticationProviderProperties.Saml samlProperties;
private AuthenticationProviderProperties.Cas casProperties;
private Map<String, AuthenticationProviderProperties.Oauth> oauthProperties;
@Autowired
private ServletContext servletContext;
@Autowired
private AuthenticationProviderProperties providerProperties;
@Autowired(required = false)
private OidcClient oidcClient;
@Autowired(required = false)
private TwitterClient twitterClient;
@Autowired(required = false)
private GoogleOidcClient googleOidcClient;
@Autowired(required = false)
private FacebookClient facebookClient;
@Autowired(required = false)
private SAML2Client saml2Client;
@Autowired(required = false)
private CasAuthenticationEntryPoint casEntryPoint;
@Autowired
private UserService userService;
private static final Logger logger = LoggerFactory.getLogger(AuthenticationController.class);
public AuthenticationController(final SystemProperties systemProperties,
final AuthenticationProviderProperties authenticationProviderProperties) {
apiPath = systemProperties.getApi().getProxyPath();
guestProperties = authenticationProviderProperties.getGuest();
registeredProperties = authenticationProviderProperties.getRegistered();
ldapProperties = authenticationProviderProperties.getLdap();
oidcProperties = authenticationProviderProperties.getOidc();
samlProperties = authenticationProviderProperties.getSaml();
casProperties = authenticationProviderProperties.getCas();
oauthProperties = authenticationProviderProperties.getOauth();
}
@PostConstruct
private void init() {
if (apiPath == null || "".equals(apiPath)) {
apiPath = servletContext.getContextPath();
}
}
@PostMapping({ "/login", "/doLogin" })
public void doLogin(
@RequestParam("type") final String type,
@RequestParam(value = "user", required = false) final String username,
@RequestParam(required = false) final String password,
final HttpServletRequest request,
final HttpServletResponse response
) throws IOException {
final String address = request.getRemoteAddr();
if (userService.isBannedFromLogin(address)) {
response.sendError(429, "Too Many Requests");
return;
}
final UsernamePasswordAuthenticationToken authRequest =
new UsernamePasswordAuthenticationToken(username, password);
if (registeredProperties.isEnabled() && "arsnova".equals(type)) {
try {
userService.authenticate(authRequest, UserProfile.AuthProvider.ARSNOVA, address);
} catch (final AuthenticationException e) {
logger.info("Database authentication failed.", e);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
}
} else if (ldapProperties.stream().anyMatch(p -> p.isEnabled()) && "ldap".equals(type)) {
try {
userService.authenticate(authRequest, UserProfile.AuthProvider.LDAP, address);
} catch (final AuthenticationException e) {
logger.info("LDAP authentication failed.", e);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
}
} else if (guestProperties.isEnabled() && "guest".equals(type)) {
try {
userService.authenticate(authRequest, UserProfile.AuthProvider.ARSNOVA_GUEST, address);
} catch (final AuthenticationException e) {
logger.debug("Guest authentication failed.", e);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
}
} else {
response.setStatus(HttpStatus.BAD_REQUEST.value());
}
}
@GetMapping("/dialog")
@ResponseBody
public View dialog(
@RequestParam("type") final String type,
@RequestParam(value = "successurl", defaultValue = "/") String successUrl,
@RequestParam(value = "failureurl", defaultValue = "/") String failureUrl,
final HttpServletRequest request,
final HttpServletResponse response
) throws HttpAction, IOException, ServletException {
View result = null;
/* Use URLs from a request parameters for redirection as long as the
* URL is not absolute (to prevent abuse of the redirection). */
if (UrlUtils.isAbsoluteUrl(successUrl)) {
successUrl = "/";
}
if (UrlUtils.isAbsoluteUrl(failureUrl)) {
failureUrl = "/";
}
final String host = request.getServerName();
final int port = request.getServerPort();
final String scheme = request.getScheme();
String serverUrl = scheme + "://" + host;
if ("https".equals(scheme)) {
if (443 != port) {
serverUrl = serverUrl + ":" + String.valueOf(port);
}
} else {
if (80 != port) {
serverUrl = serverUrl + ":" + String.valueOf(port);
}
}
request.getSession().setAttribute(LoginAuthenticationSucessHandler.URL_ATTRIBUTE, serverUrl + successUrl);
request.getSession().setAttribute(LoginAuthenticationFailureHandler.URL_ATTRIBUTE, serverUrl + failureUrl);
if (casProperties.isEnabled() && "cas".equals(type)) {
casEntryPoint.commence(request, response, null);
} else if (saml2Client != null && "saml".equals(type)) {
result = new RedirectView(
saml2Client.getRedirectAction(new J2EContext(request, response)).getLocation());
} else if (oidcProperties.stream().anyMatch(p -> p.isEnabled()) && "oidc".equals(type)) {
result = new RedirectView(
oidcClient.getRedirectAction(new J2EContext(request, response)).getLocation());
} else if (twitterClient != null && "twitter".equals(type)) {
result = new RedirectView(
twitterClient.getRedirectAction(new J2EContext(request, response)).getLocation());
} else if (facebookClient != null && "facebook".equals(type)) {
facebookClient.setFields("id");
facebookClient.setScope("");
result = new RedirectView(
facebookClient.getRedirectAction(new J2EContext(request, response)).getLocation());
} else if (googleOidcClient != null && "google".equals(type)) {
result = new RedirectView(
googleOidcClient.getRedirectAction(new J2EContext(request, response)).getLocation());
} else {
response.setStatus(HttpStatus.BAD_REQUEST.value());
}
return result;
}
@GetMapping({ "/", "/whoami" })
@ResponseBody
public ClientAuthentication whoami(@AuthenticationPrincipal final User user) {
if (user == null) {
throw new UnauthorizedException();
}
return new ClientAuthentication(user);
}
@PostMapping("/logout")
public String doLogout(final HttpServletRequest request) {
final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
userService.removeUserIdFromMaps(userService.getCurrentUser().getId());
request.getSession().invalidate();
SecurityContextHolder.clearContext();
if (auth instanceof CasAuthenticationToken) {
return "redirect:" + apiPath + SecurityConfig.CAS_LOGOUT_PATH;
}
return "redirect:" + (request.getHeader("referer") != null ? request.getHeader("referer") : "/");
}
@GetMapping("/services")
@ResponseBody
public List<ServiceDescription> getServices(final HttpServletRequest request) {
final List<ServiceDescription> services = new ArrayList<>();
/* The first parameter is replaced by the backend, the second one by the frondend */
final String dialogUrl = apiPath + "/v2/auth/dialog?type={0}&successurl='{0}'";
if (guestProperties.isEnabled()) {
final ServiceDescription sdesc = new ServiceDescription(
"guest",
"Guest",
null,
guestProperties.getAllowedRoles()
);
sdesc.setOrder(guestProperties.getOrder());
services.add(sdesc);
}
if (registeredProperties.isEnabled()) {
final ServiceDescription sdesc = new ServiceDescription(
"arsnova",
registeredProperties.getTitle(),
customizationPath + "/login?provider=arsnova&redirect={0}",
registeredProperties.getAllowedRoles()
);
sdesc.setOrder(registeredProperties.getOrder());
services.add(sdesc);
}
if (ldapProperties.get(0).isEnabled()) {
final ServiceDescription sdesc = new ServiceDescription(
SecurityConfig.LDAP_PROVIDER_ID,
ldapProperties.get(0).getTitle(),
customizationPath + "/login?provider=" + SecurityConfig.LDAP_PROVIDER_ID + "&redirect={0}",
ldapProperties.get(0).getAllowedRoles()
);
sdesc.setOrder(ldapProperties.get(0).getOrder());
services.add(sdesc);
}
if (samlProperties.isEnabled()) {
final ServiceDescription sdesc = new ServiceDescription(
SecurityConfig.SAML_PROVIDER_ID,
samlProperties.getTitle(),
MessageFormat.format(dialogUrl, SecurityConfig.SAML_PROVIDER_ID),
samlProperties.getAllowedRoles()
);
sdesc.setOrder(samlProperties.getOrder());
services.add(sdesc);
}
if (casProperties.isEnabled()) {
final ServiceDescription sdesc = new ServiceDescription(
SecurityConfig.CAS_PROVIDER_ID,
casProperties.getTitle(),
MessageFormat.format(dialogUrl, SecurityConfig.CAS_PROVIDER_ID),
casProperties.getAllowedRoles()
);
sdesc.setOrder(casProperties.getOrder());
services.add(sdesc);
}
if (oidcProperties.get(0).isEnabled()) {
final ServiceDescription sdesc = new ServiceDescription(
SecurityConfig.OIDC_PROVIDER_ID,
oidcProperties.get(0).getTitle(),
MessageFormat.format(dialogUrl, SecurityConfig.OIDC_PROVIDER_ID),
oidcProperties.get(0).getAllowedRoles()
);
sdesc.setOrder(oidcProperties.get(0).getOrder());
services.add(sdesc);
}
final AuthenticationProviderProperties.Oauth facebookProperties =
oauthProperties.get(SecurityConfig.FACEBOOK_PROVIDER_ID);
if (facebookProperties != null && facebookProperties.isEnabled()) {
final ServiceDescription sdesc = new ServiceDescription(
SecurityConfig.FACEBOOK_PROVIDER_ID,
"Facebook",
MessageFormat.format(dialogUrl, SecurityConfig.FACEBOOK_PROVIDER_ID),
facebookProperties.getAllowedRoles()
);
sdesc.setOrder(facebookProperties.getOrder());
services.add(sdesc);
}
final AuthenticationProviderProperties.Oauth googleProperties =
oauthProperties.get(SecurityConfig.GOOGLE_PROVIDER_ID);
if (googleProperties != null && googleProperties.isEnabled()) {
final ServiceDescription sdesc = new ServiceDescription(
SecurityConfig.GOOGLE_PROVIDER_ID,
"Google",
MessageFormat.format(dialogUrl, SecurityConfig.GOOGLE_PROVIDER_ID),
googleProperties.getAllowedRoles()
);
sdesc.setOrder(googleProperties.getOrder());
services.add(sdesc);
}
final AuthenticationProviderProperties.Oauth twitterProperties =
oauthProperties.get(SecurityConfig.TWITTER_PROVIDER_ID);
if (twitterProperties != null && twitterProperties.isEnabled()) {
final ServiceDescription sdesc = new ServiceDescription(
SecurityConfig.TWITTER_PROVIDER_ID,
"Twitter",
MessageFormat.format(dialogUrl, SecurityConfig.TWITTER_PROVIDER_ID),
twitterProperties.getAllowedRoles()
);
sdesc.setOrder(twitterProperties.getOrder());
services.add(sdesc);
}
return services;
}
private Collection<GrantedAuthority> getAuthorities(final boolean admin) {
final List<GrantedAuthority> authList = new ArrayList<>();
authList.add(new SimpleGrantedAuthority("ROLE_USER"));
if (admin) {
authList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
}
return authList;
}
}