SEBSERV-418 finished implementation for teacher account and login
This commit is contained in:
parent
1d332fc579
commit
908665ddcc
27 changed files with 336 additions and 178 deletions
|
@ -138,6 +138,8 @@ public final class Constants {
|
|||
public static final String XML_PLIST_INTEGER = "integer";
|
||||
public static final String XML_PLIST_REAL = "real";
|
||||
|
||||
public static final String OAUTH2_GRANT_TYPE = "grant_type";
|
||||
public static final String OAUTH2_USER_NAME = "username";
|
||||
public static final String OAUTH2_GRANT_TYPE_PASSWORD = "password";
|
||||
public static final String OAUTH2_CLIENT_SECRET = "client_secret";
|
||||
public static final String OAUTH2_GRANT_TYPE_REFRESH_TOKEN = "refresh_token";
|
||||
|
|
|
@ -8,26 +8,27 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gbl.model.user;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
|
||||
public class TokenLoginInfo {
|
||||
@JsonProperty
|
||||
@JsonProperty("username")
|
||||
public final String username;
|
||||
@JsonProperty
|
||||
@JsonProperty("userUUID")
|
||||
public final String userUUID;
|
||||
@JsonProperty
|
||||
public final String redirect;
|
||||
@JsonProperty
|
||||
@JsonProperty("redirect")
|
||||
public final EntityKey redirect;
|
||||
@JsonProperty("login")
|
||||
public final OAuth2AccessToken login;
|
||||
|
||||
@JsonCreator
|
||||
public TokenLoginInfo(
|
||||
@JsonProperty final String username,
|
||||
@JsonProperty final String userUUID,
|
||||
@JsonProperty final String redirect,
|
||||
@JsonProperty final OAuth2AccessToken login) {
|
||||
@JsonProperty("username") final String username,
|
||||
@JsonProperty("userUUID") final String userUUID,
|
||||
@JsonProperty("redirect") final EntityKey redirect,
|
||||
@JsonProperty("login") final OAuth2AccessToken login) {
|
||||
|
||||
this.username = username;
|
||||
this.userUUID = userUUID;
|
||||
|
|
|
@ -50,7 +50,7 @@ public enum UserRole implements Entity, GrantedAuthority {
|
|||
public static List<UserRole> publicRolesForUser(final UserInfo user) {
|
||||
final EnumSet<UserRole> roles = user.getUserRoles();
|
||||
if (roles.contains(SEB_SERVER_ADMIN)) {
|
||||
return Arrays.asList(SEB_SERVER_ADMIN, INSTITUTIONAL_ADMIN, EXAM_ADMIN, EXAM_SUPPORTER);
|
||||
return Arrays.asList(SEB_SERVER_ADMIN, INSTITUTIONAL_ADMIN, EXAM_ADMIN, EXAM_SUPPORTER, TEACHER);
|
||||
} else if (roles.contains(INSTITUTIONAL_ADMIN)) {
|
||||
return Arrays.asList(INSTITUTIONAL_ADMIN, EXAM_ADMIN, EXAM_SUPPORTER);
|
||||
} else if (roles.contains(EXAM_ADMIN)) {
|
||||
|
|
|
@ -91,6 +91,7 @@ public class GuiWebsecurityConfig extends WebSecurityConfigurerAdapter {
|
|||
.antMatchers(adminAPIEndpoint + API.INFO_ENDPOINT + API.LOGO_PATH_SEGMENT + "/**").permitAll()
|
||||
.antMatchers(adminAPIEndpoint + API.INFO_ENDPOINT + API.INFO_INST_PATH_SEGMENT + "/**").permitAll()
|
||||
.antMatchers(adminAPIEndpoint + API.REGISTER_ENDPOINT).permitAll()
|
||||
.antMatchers(API.OAUTH_JWT_TOKEN_ENDPOINT + "/**").permitAll()
|
||||
.and()
|
||||
.antMatcher("/**")
|
||||
.authorizeRequests()
|
||||
|
|
|
@ -21,6 +21,8 @@ import javax.servlet.ServletException;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.SEBServerAuthorizationContext;
|
||||
import org.apache.commons.codec.binary.Base64InputStream;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
|
@ -49,6 +51,8 @@ import ch.ethz.seb.sebserver.gbl.model.EntityName;
|
|||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.WebserviceURIService;
|
||||
import ch.ethz.seb.sebserver.gui.widget.ImageUploadSelection;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.support.WebApplicationContextUtils;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
|
@ -116,6 +120,20 @@ public final class InstitutionalAuthenticationEntryPoint implements Authenticati
|
|||
final HttpServletResponse response,
|
||||
final AuthenticationException authException) throws IOException, ServletException {
|
||||
|
||||
final String jwt = request.getParameter("jwt");
|
||||
if (StringUtils.isNotBlank(jwt)) {
|
||||
final WebApplicationContext webApplicationContext = WebApplicationContextUtils
|
||||
.getRequiredWebApplicationContext(request.getServletContext());
|
||||
final AuthorizationContextHolder authorizationContextHolder = webApplicationContext
|
||||
.getBean(AuthorizationContextHolder.class);
|
||||
final SEBServerAuthorizationContext authorizationContext = authorizationContextHolder
|
||||
.getAuthorizationContext(request.getSession());
|
||||
if (authorizationContext.autoLogin(jwt)) {
|
||||
forwardToEntryPoint(request, response, this.guiEntryPoint, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final String institutionalEndpoint = extractInstitutionalEndpoint(request);
|
||||
|
||||
if (StringUtils.isNotBlank(institutionalEndpoint)) {
|
||||
|
@ -168,7 +186,6 @@ public final class InstitutionalAuthenticationEntryPoint implements Authenticati
|
|||
request.getSession().removeAttribute(API.PARAM_LOGO_IMAGE);
|
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||
forwardToEntryPoint(request, response, this.guiEntryPoint, institutionalEndpoint == null);
|
||||
|
||||
}
|
||||
|
||||
private void forwardToEntryPoint(
|
||||
|
|
|
@ -93,103 +93,105 @@ public class ActivitiesPane implements TemplateComposer {
|
|||
|
||||
final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(pageContext);
|
||||
|
||||
final boolean isTeacherOnly = this.currentUser.get().hasAnyRole(UserRole.TEACHER) &&
|
||||
!this.currentUser.get().hasAnyRole(UserRole.EXAM_SUPPORTER) &&
|
||||
!this.currentUser.get().hasAnyRole(UserRole.EXAM_ADMIN) ;
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// ---- SEB ADMIN ----------------------------------------------------------------------
|
||||
|
||||
final boolean isServerOrInstAdmin = this.currentUser.get()
|
||||
.hasAnyRole(UserRole.SEB_SERVER_ADMIN, UserRole.INSTITUTIONAL_ADMIN);
|
||||
|
||||
// SEB Server Administration
|
||||
final TreeItem sebAdmin = this.widgetFactory.treeItemLocalized(
|
||||
navigation,
|
||||
ActivityDefinition.SEB_ADMINISTRATION.displayName);
|
||||
if (isServerOrInstAdmin) {
|
||||
// SEB Server Administration
|
||||
final TreeItem sebAdmin = this.widgetFactory.treeItemLocalized(
|
||||
navigation,
|
||||
ActivityDefinition.SEB_ADMINISTRATION.displayName);
|
||||
|
||||
// Institution
|
||||
// Institution
|
||||
|
||||
if (currentUser.isFeatureEnabled(UserFeatures.Feature.ADMIN_INSTITUTION)) {
|
||||
// If current user has SEB Server Admin role, show the Institution list
|
||||
if (userInfo.hasRole(UserRole.SEB_SERVER_ADMIN)) {
|
||||
// institutions (list) as root
|
||||
final TreeItem institutions = this.widgetFactory.treeItemLocalized(
|
||||
if (currentUser.isFeatureEnabled(UserFeatures.Feature.ADMIN_INSTITUTION)) {
|
||||
// If current user has SEB Server Admin role, show the Institution list
|
||||
if (userInfo.hasRole(UserRole.SEB_SERVER_ADMIN)) {
|
||||
// institutions (list) as root
|
||||
final TreeItem institutions = this.widgetFactory.treeItemLocalized(
|
||||
sebAdmin,
|
||||
ActivityDefinition.INSTITUTION.displayName);
|
||||
injectActivitySelection(
|
||||
institutions,
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.INSTITUTION_VIEW_LIST)
|
||||
.create());
|
||||
|
||||
} else if (userInfo.hasRole(UserRole.INSTITUTIONAL_ADMIN)) {
|
||||
// otherwise show the form of the institution for current user
|
||||
final TreeItem institutions = this.widgetFactory.treeItemLocalized(
|
||||
sebAdmin,
|
||||
ActivityDefinition.INSTITUTION.displayName);
|
||||
injectActivitySelection(
|
||||
institutions,
|
||||
actionBuilder.newAction(ActionDefinition.INSTITUTION_VIEW_FORM)
|
||||
.withEntityKey(userInfo.institutionId, EntityType.INSTITUTION)
|
||||
.withAttribute(AttributeKeys.READ_ONLY, "true")
|
||||
.create());
|
||||
}
|
||||
}
|
||||
|
||||
// User Account
|
||||
// if current user has role seb-server admin or institutional-admin, show list
|
||||
if (!pageService.isLightSetup() && currentUser.isFeatureEnabled(UserFeatures.Feature.ADMIN_USER_ADMINISTRATION)) {
|
||||
|
||||
final TreeItem userAccounts = this.widgetFactory.treeItemLocalized(
|
||||
sebAdmin,
|
||||
ActivityDefinition.INSTITUTION.displayName);
|
||||
ActivityDefinition.USER_ACCOUNT.displayName);
|
||||
injectActivitySelection(
|
||||
institutions,
|
||||
userAccounts,
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.INSTITUTION_VIEW_LIST)
|
||||
.newAction(ActionDefinition.USER_ACCOUNT_VIEW_LIST)
|
||||
.create());
|
||||
|
||||
} else if (userInfo.hasRole(UserRole.INSTITUTIONAL_ADMIN)) {
|
||||
// otherwise show the form of the institution for current user
|
||||
final TreeItem institutions = this.widgetFactory.treeItemLocalized(
|
||||
} else if (currentUser.isFeatureEnabled(UserFeatures.Feature.ADMIN_USER_ACCOUNT)) {
|
||||
// otherwise show the user account form for current user
|
||||
final TreeItem userAccounts = pageService.isLightSetup() || !currentUser.isFeatureEnabled(UserFeatures.Feature.ADMIN_USER_ADMINISTRATION)
|
||||
? this.widgetFactory.treeItemLocalized(
|
||||
sebAdmin,
|
||||
ActivityDefinition.INSTITUTION.displayName);
|
||||
ActivityDefinition.USER_ACCOUNT.displayName)
|
||||
: this.widgetFactory.treeItemLocalized(
|
||||
navigation,
|
||||
ActivityDefinition.USER_ACCOUNT.displayName);
|
||||
injectActivitySelection(
|
||||
institutions,
|
||||
actionBuilder.newAction(ActionDefinition.INSTITUTION_VIEW_FORM)
|
||||
.withEntityKey(userInfo.institutionId, EntityType.INSTITUTION)
|
||||
userAccounts,
|
||||
actionBuilder.newAction(ActionDefinition.USER_ACCOUNT_VIEW_FORM)
|
||||
.withEntityKey(this.currentUser.get().getEntityKey())
|
||||
.withAttribute(AttributeKeys.READ_ONLY, "true")
|
||||
.create());
|
||||
}
|
||||
|
||||
// User Activity Logs
|
||||
final boolean viewUserActivityLogs = this.currentUser.hasInstitutionalPrivilege(
|
||||
PrivilegeType.READ,
|
||||
EntityType.USER_ACTIVITY_LOG)
|
||||
&& currentUser.isFeatureEnabled(UserFeatures.Feature.ADMIN_AUDIT_LOGS);
|
||||
if (viewUserActivityLogs) {
|
||||
final TreeItem activityLogs = this.widgetFactory.treeItemLocalized(
|
||||
sebAdmin,
|
||||
ActivityDefinition.USER_ACTIVITY_LOGS.displayName);
|
||||
injectActivitySelection(
|
||||
activityLogs,
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.LOGS_USER_ACTIVITY_LIST)
|
||||
.create());
|
||||
}
|
||||
|
||||
if (sebAdmin.getItemCount() > 0) {
|
||||
sebAdmin.setExpanded(this.currentUser.get().hasAnyRole(
|
||||
UserRole.SEB_SERVER_ADMIN,
|
||||
UserRole.INSTITUTIONAL_ADMIN));
|
||||
} else {
|
||||
sebAdmin.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// User Account
|
||||
// if current user has role seb-server admin or institutional-admin, show list
|
||||
if (isServerOrInstAdmin
|
||||
&& !pageService.isLightSetup()
|
||||
&& currentUser.isFeatureEnabled(UserFeatures.Feature.ADMIN_USER_ADMINISTRATION)) {
|
||||
|
||||
final TreeItem userAccounts = this.widgetFactory.treeItemLocalized(
|
||||
sebAdmin,
|
||||
ActivityDefinition.USER_ACCOUNT.displayName);
|
||||
injectActivitySelection(
|
||||
userAccounts,
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.USER_ACCOUNT_VIEW_LIST)
|
||||
.create());
|
||||
} else if (currentUser.isFeatureEnabled(UserFeatures.Feature.ADMIN_USER_ACCOUNT)) {
|
||||
// otherwise show the user account form for current user
|
||||
final TreeItem userAccounts = pageService.isLightSetup() || !currentUser.isFeatureEnabled(UserFeatures.Feature.ADMIN_USER_ADMINISTRATION)
|
||||
? this.widgetFactory.treeItemLocalized(
|
||||
sebAdmin,
|
||||
ActivityDefinition.USER_ACCOUNT.displayName)
|
||||
: this.widgetFactory.treeItemLocalized(
|
||||
navigation,
|
||||
ActivityDefinition.USER_ACCOUNT.displayName);
|
||||
injectActivitySelection(
|
||||
userAccounts,
|
||||
actionBuilder.newAction(ActionDefinition.USER_ACCOUNT_VIEW_FORM)
|
||||
.withEntityKey(this.currentUser.get().getEntityKey())
|
||||
.withAttribute(AttributeKeys.READ_ONLY, "true")
|
||||
.create());
|
||||
}
|
||||
|
||||
// User Activity Logs
|
||||
final boolean viewUserActivityLogs = this.currentUser.hasInstitutionalPrivilege(
|
||||
PrivilegeType.READ,
|
||||
EntityType.USER_ACTIVITY_LOG)
|
||||
&& currentUser.isFeatureEnabled(UserFeatures.Feature.ADMIN_AUDIT_LOGS);
|
||||
if (viewUserActivityLogs) {
|
||||
final TreeItem activityLogs = this.widgetFactory.treeItemLocalized(
|
||||
sebAdmin,
|
||||
ActivityDefinition.USER_ACTIVITY_LOGS.displayName);
|
||||
injectActivitySelection(
|
||||
activityLogs,
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.LOGS_USER_ACTIVITY_LIST)
|
||||
.create());
|
||||
}
|
||||
|
||||
if (sebAdmin.getItemCount() > 0) {
|
||||
sebAdmin.setExpanded(this.currentUser.get().hasAnyRole(
|
||||
UserRole.SEB_SERVER_ADMIN,
|
||||
UserRole.INSTITUTIONAL_ADMIN));
|
||||
} else {
|
||||
sebAdmin.dispose();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ---- SEB ADMIN ----------------------------------------------------------------------
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
|
@ -252,7 +254,7 @@ public class ActivitiesPane implements TemplateComposer {
|
|||
}
|
||||
|
||||
// Certificate management
|
||||
if (!isSupporterOnly && certificatesEnabled) {
|
||||
if (certificatesEnabled) {
|
||||
final TreeItem examConfigTemplate = this.widgetFactory.treeItemLocalized(
|
||||
sebConfigs,
|
||||
ActivityDefinition.SEB_CERTIFICATE_MANAGEMENT.displayName);
|
||||
|
@ -283,7 +285,7 @@ public class ActivitiesPane implements TemplateComposer {
|
|||
final boolean examTemplateEnabled = currentUser.isFeatureEnabled(UserFeatures.Feature.EXAM_TEMPLATE);
|
||||
final boolean anyExamAdminEnabled = lmsSetupEnabled || quizLookupEnabled || examEnabled || examTemplateEnabled;
|
||||
|
||||
if (anyExamAdminEnabled) {
|
||||
if (anyExamAdminEnabled && !isTeacherOnly) {
|
||||
// Exam Administration
|
||||
final TreeItem examAdmin = this.widgetFactory.treeItemLocalized(
|
||||
navigation,
|
||||
|
@ -369,7 +371,7 @@ public class ActivitiesPane implements TemplateComposer {
|
|||
ActivityDefinition.MONITORING.displayName);
|
||||
|
||||
// Monitoring exams
|
||||
if (isSupporter) {
|
||||
if (isSupporter || isTeacherOnly) {
|
||||
|
||||
if (monitoringEnabled) {
|
||||
final TreeItem monitoringExams = this.widgetFactory.treeItemLocalized(
|
||||
|
@ -382,7 +384,7 @@ public class ActivitiesPane implements TemplateComposer {
|
|||
.create());
|
||||
}
|
||||
|
||||
if (finishedEnabled) {
|
||||
if (finishedEnabled && !isTeacherOnly) {
|
||||
final TreeItem finishedExams = this.widgetFactory.treeItemLocalized(
|
||||
monitoring,
|
||||
ActivityDefinition.FINISHED_EXAMS.displayName);
|
||||
|
@ -395,7 +397,7 @@ public class ActivitiesPane implements TemplateComposer {
|
|||
}
|
||||
|
||||
// SEB Client Logs
|
||||
if (viewSEBClientLogs) {
|
||||
if (viewSEBClientLogs && !isTeacherOnly) {
|
||||
final TreeItem sebLogs = (isSupporter)
|
||||
? this.widgetFactory.treeItemLocalized(
|
||||
monitoring,
|
||||
|
@ -414,7 +416,7 @@ public class ActivitiesPane implements TemplateComposer {
|
|||
monitoring.setExpanded(
|
||||
this.currentUser
|
||||
.get()
|
||||
.hasAnyRole(UserRole.EXAM_SUPPORTER));
|
||||
.hasAnyRole(UserRole.EXAM_SUPPORTER, UserRole.TEACHER));
|
||||
} else {
|
||||
monitoring.dispose();
|
||||
}
|
||||
|
@ -486,7 +488,7 @@ public class ActivitiesPane implements TemplateComposer {
|
|||
return findItemByActionDefinition(
|
||||
navigation.getItems(),
|
||||
ActivityDefinition.SEB_EXAM_CONFIG);
|
||||
} else if (this.currentUser.get().hasAnyRole(UserRole.EXAM_SUPPORTER)) {
|
||||
} else if (this.currentUser.get().hasAnyRole(UserRole.EXAM_SUPPORTER, UserRole.TEACHER)) {
|
||||
return findItemByActionDefinition(
|
||||
navigation.getItems(),
|
||||
ActivityDefinition.MONITORING_EXAMS);
|
||||
|
|
|
@ -95,6 +95,9 @@ public class FinishedExamList implements TemplateComposer {
|
|||
final RestService restService = this.resourceService.getRestService();
|
||||
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
|
||||
|
||||
boolean roleBasedAccess = currentUser.get()
|
||||
.hasAnyRole(UserRole.EXAM_SUPPORTER);
|
||||
|
||||
// content page layout with title
|
||||
final Composite content = widgetFactory.defaultPageLayout(
|
||||
pageContext.getParent(),
|
||||
|
@ -165,7 +168,7 @@ public class FinishedExamList implements TemplateComposer {
|
|||
table::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER), false);
|
||||
.publishIf(() -> roleBasedAccess, false);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -205,7 +205,7 @@ public class MonitoringClientConnection implements TemplateComposer {
|
|||
.onError(error -> pageContext.notifyLoadError(EntityType.EXAM, error))
|
||||
.getOrThrow();
|
||||
final UserInfo user = currentUser.get();
|
||||
final boolean supporting = user.hasRole(UserRole.EXAM_SUPPORTER) &&
|
||||
final boolean supporting = user.hasAnyRole(UserRole.EXAM_SUPPORTER, UserRole.TEACHER) &&
|
||||
exam.supporter.contains(user.uuid);
|
||||
final BooleanSupplier isExamSupporter = () -> supporting || user.hasRole(UserRole.EXAM_ADMIN);
|
||||
|
||||
|
|
|
@ -156,7 +156,7 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
final boolean quitEnabled = currentUser.isFeatureEnabled(MONITORING_RUNNING_EXAM_QUIT);
|
||||
final boolean lockscreenEnabled = currentUser.isFeatureEnabled(MONITORING_RUNNING_EXAM_QUIT);
|
||||
final boolean cancelEnabled = currentUser.isFeatureEnabled(MONITORING_RUNNING_EXAM_CANCEL_CON);
|
||||
final boolean supporting = user.hasRole(UserRole.EXAM_SUPPORTER) &&
|
||||
final boolean supporting = user.hasAnyRole(UserRole.EXAM_SUPPORTER, UserRole.TEACHER) &&
|
||||
exam.supporter.contains(user.uuid);
|
||||
final BooleanSupplier isExamSupporter = () -> supporting || user.hasRole(UserRole.EXAM_ADMIN);
|
||||
|
||||
|
|
|
@ -86,6 +86,9 @@ public class MonitoringRunningExamList implements TemplateComposer {
|
|||
final RestService restService = this.resourceService.getRestService();
|
||||
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
|
||||
|
||||
boolean hasRoleBasedAccess = currentUser.get().hasAnyRole(
|
||||
UserRole.EXAM_SUPPORTER, UserRole.TEACHER);
|
||||
|
||||
// content page layout with title
|
||||
final Composite content = widgetFactory.defaultPageLayout(
|
||||
pageContext.getParent(),
|
||||
|
@ -149,7 +152,7 @@ public class MonitoringRunningExamList implements TemplateComposer {
|
|||
table::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER), false);
|
||||
.publishIf(() -> hasRoleBasedAccess, false);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -11,13 +11,11 @@ package ch.ethz.seb.sebserver.gui.service.remote.webservice.auth;
|
|||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.TokenLoginInfo;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -25,9 +23,7 @@ import org.slf4j.LoggerFactory;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
|
@ -146,12 +142,14 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol
|
|||
|
||||
private boolean valid = true;
|
||||
|
||||
private final ClientHttpRequestFactory clientHttpRequestFactory;
|
||||
private final ResourceOwnerPasswordResourceDetails resource;
|
||||
private final DisposableOAuth2RestTemplate restTemplate;
|
||||
private final String revokeTokenURI;
|
||||
private final String currentUserURI;
|
||||
private final String loginLogURI;
|
||||
private final String logoutLogURI;
|
||||
private final String jwtTokenVerificationURI;
|
||||
|
||||
private Result<UserInfo> loggedInUser = null;
|
||||
|
||||
|
@ -161,6 +159,7 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol
|
|||
final WebserviceURIService webserviceURIService,
|
||||
final ClientHttpRequestFactory clientHttpRequestFactory) {
|
||||
|
||||
this.clientHttpRequestFactory = clientHttpRequestFactory;
|
||||
this.resource = new ResourceOwnerPasswordResourceDetails();
|
||||
this.resource.setAccessTokenUri(webserviceURIService.getOAuthTokenURI());
|
||||
this.resource.setClientId(guiClientId);
|
||||
|
@ -179,6 +178,7 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol
|
|||
this.currentUserURI = webserviceURIService.getCurrentUserRequestURI();
|
||||
this.loginLogURI = webserviceURIService.getLoginLogPostURI();
|
||||
this.logoutLogURI = webserviceURIService.getLogoutLogPostURI();
|
||||
this.jwtTokenVerificationURI = webserviceURIService.getJWTTokenVerificationURI();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -193,9 +193,6 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol
|
|||
return false;
|
||||
}
|
||||
|
||||
// TODO check if this is needed. If not remove it.
|
||||
// This gets called many times for a page load
|
||||
|
||||
try {
|
||||
final ResponseEntity<String> forEntity =
|
||||
this.restTemplate.getForEntity(this.currentUserURI, String.class);
|
||||
|
@ -264,8 +261,32 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol
|
|||
|
||||
@Override
|
||||
public boolean autoLogin(final String oneTimeToken) {
|
||||
// TODO call auto-login API on Webservice to verify the oneTimeToken and
|
||||
return false;
|
||||
try {
|
||||
// Create ad-hoc RestTemplate and call token verification
|
||||
final RestTemplate verifyTemplate = new RestTemplate(this.clientHttpRequestFactory);
|
||||
final HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.set("ONE_TIME_TOKEN_TO_VERIFY", oneTimeToken);
|
||||
httpHeaders.setBasicAuth(resource.getClientId(), resource.getClientSecret());
|
||||
|
||||
final ResponseEntity<TokenLoginInfo> response = verifyTemplate.exchange(
|
||||
this.jwtTokenVerificationURI,
|
||||
HttpMethod.POST,
|
||||
new HttpEntity<TokenLoginInfo>(null, httpHeaders),
|
||||
TokenLoginInfo.class);
|
||||
|
||||
if (response.getStatusCodeValue() != HttpStatus.OK.value()) {
|
||||
log.warn("Autologin failed due to error response: {}", response);
|
||||
return false;
|
||||
}
|
||||
|
||||
final TokenLoginInfo loginInfo = response.getBody();
|
||||
this.restTemplate.getOAuth2ClientContext().setAccessToken(loginInfo.login);
|
||||
|
||||
return this.isLoggedIn();
|
||||
} catch (final Exception e) {
|
||||
log.warn("Autologin failed due to unexpected error: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -83,4 +83,11 @@ public class WebserviceURIService {
|
|||
.path(API.USER_ACCOUNT_ENDPOINT + API.LOGOUT_PATH_SEGMENT)
|
||||
.toUriString();
|
||||
}
|
||||
|
||||
public String getJWTTokenVerificationURI() {
|
||||
return UriComponentsBuilder.fromHttpUrl(this.webserviceServerAddress)
|
||||
.path(this.contextPath)
|
||||
.path(API.OAUTH_JWT_TOKEN_VERIFY_ENDPOINT)
|
||||
.toUriString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,35 +8,75 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.authorization;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.TokenLoginInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
|
||||
|
||||
/** Service used to maintain Teacher Ad-Hoc Accounts */
|
||||
public interface TeacherAccountService {
|
||||
|
||||
/** Creates an Ad-Hoc Teacher account for a given existing Exam.
|
||||
* This also checks if such an account already exists and if so,
|
||||
* it uses that and activates it if not already active
|
||||
*
|
||||
* @param exam The Exam instance
|
||||
* @param adHocAccountData The account data for new Ad-Hoc account
|
||||
* @return Result refer to the accounts UserInfo instance or to an error when happened.
|
||||
*/
|
||||
Result<UserInfo> createNewTeacherAccountForExam(
|
||||
Exam exam,
|
||||
final FullLmsIntegrationService.AdHocAccountData adHocAccountData);
|
||||
|
||||
/** Get the identifier for certain Teacher account for specified Exam.
|
||||
*
|
||||
* @param exam The Exam instance
|
||||
* @param adHocAccountData the account data
|
||||
* @return account identifier
|
||||
*/
|
||||
default String getTeacherAccountIdentifier(
|
||||
final Exam exam,
|
||||
final FullLmsIntegrationService.AdHocAccountData adHocAccountData) {
|
||||
return getTeacherAccountIdentifier(exam.getModelId(), adHocAccountData.userId);
|
||||
}
|
||||
|
||||
/** Get the identifier for certain Teacher account for specified Exam.
|
||||
*
|
||||
* @param examId The Exam identifier
|
||||
* @param userId the account id
|
||||
* @return account identifier
|
||||
*/
|
||||
String getTeacherAccountIdentifier(String examId, String userId);
|
||||
|
||||
Result<UserInfo> createNewTeacherAccountForExam(
|
||||
Exam exam,
|
||||
final FullLmsIntegrationService.AdHocAccountData adHocAccountData);
|
||||
|
||||
/** Deactivates a certain ad-hoc Teacher account
|
||||
* Usually called when an exam is deleted. Checks if Teacher account for exam
|
||||
* is not used by other active exams and if so, deactivates unused ad-hoc accounts
|
||||
*
|
||||
* @param exam The Exam for witch to deactivate all applied ad-hoc Teacher accounts
|
||||
* if they are not used anymore.
|
||||
* @return Result refer to the given exam or to an error when happened
|
||||
*/
|
||||
Result<Exam> deactivateTeacherAccountsForExam(Exam exam);
|
||||
|
||||
/** Get a One Time Access JWT Token for auto-login for a specific ad-hoc Teacher account.
|
||||
*
|
||||
* @param exam The involved Exam instance
|
||||
* @param adHocAccountData the account data
|
||||
* @param createIfNotExists Indicates if a ad-hoc Teacher account shall be created if there is none for given
|
||||
* account data.
|
||||
* @return Result refer to the One Time Access JWT Token or to an error when happened.
|
||||
*/
|
||||
Result<String> getOneTimeTokenForTeacherAccount(
|
||||
Exam exam,
|
||||
FullLmsIntegrationService.AdHocAccountData adHocAccountData,
|
||||
boolean createIfNotExists);
|
||||
|
||||
/** Used to verify a given One Time Access JWT Token. This must have the expected claims and must not be expired
|
||||
*
|
||||
* @param token The One Time Access JWT Token to verify access for
|
||||
* @return Result refer to the login information for auto-login or to an error when happened or access is denied
|
||||
*/
|
||||
Result<TokenLoginInfo> verifyOneTimeTokenForTeacherAccount(String token);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -123,6 +123,9 @@ public class AuthorizationServiceImpl implements AuthorizationService {
|
|||
.andForRole(UserRole.EXAM_SUPPORTER)
|
||||
.withInstitutionalPrivilege(PrivilegeType.ASSIGNED)
|
||||
.withOwnerPrivilege(PrivilegeType.MODIFY)
|
||||
.andForRole(UserRole.TEACHER)
|
||||
.withInstitutionalPrivilege(PrivilegeType.ASSIGNED)
|
||||
.withOwnerPrivilege(PrivilegeType.READ)
|
||||
.create();
|
||||
|
||||
// grants for exam templates
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.util.*;
|
|||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.TokenLoginInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
||||
|
@ -22,9 +23,7 @@ import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
|||
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.TeacherAccountService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
|
||||
|
@ -42,6 +41,7 @@ import org.springframework.http.ResponseEntity;
|
|||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.common.exceptions.UnauthorizedUserException;
|
||||
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
@ -56,13 +56,10 @@ public class TeacherAccountServiceImpl implements TeacherAccountService {
|
|||
private static final String USER_CLAIM = "usr";
|
||||
private static final String EXAM_ID_CLAIM = "exam";
|
||||
|
||||
private static final String EXAM_OTT_SUBJECT_PREFIX = "EXAM_OTT_SUBJECT_";
|
||||
|
||||
private final UserDAO userDAO;
|
||||
private final ScreenProctoringService screenProctoringService;
|
||||
private final ExamDAO examDAO;
|
||||
private final Cryptor cryptor;
|
||||
private final AdditionalAttributesDAO additionalAttributesDAO;
|
||||
final TokenEndpoint tokenEndpoint;
|
||||
private final AdminAPIClientDetails adminAPIClientDetails;
|
||||
|
||||
|
@ -71,7 +68,6 @@ public class TeacherAccountServiceImpl implements TeacherAccountService {
|
|||
final ScreenProctoringService screenProctoringService,
|
||||
final ExamDAO examDAO,
|
||||
final Cryptor cryptor,
|
||||
final AdditionalAttributesDAO additionalAttributesDAO,
|
||||
final TokenEndpoint tokenEndpoint,
|
||||
final AdminAPIClientDetails adminAPIClientDetails) {
|
||||
|
||||
|
@ -79,7 +75,6 @@ public class TeacherAccountServiceImpl implements TeacherAccountService {
|
|||
this.screenProctoringService = screenProctoringService;
|
||||
this.examDAO = examDAO;
|
||||
this.cryptor = cryptor;
|
||||
this.additionalAttributesDAO = additionalAttributesDAO;
|
||||
this.tokenEndpoint = tokenEndpoint;
|
||||
this.adminAPIClientDetails = adminAPIClientDetails;
|
||||
}
|
||||
|
@ -90,7 +85,7 @@ public class TeacherAccountServiceImpl implements TeacherAccountService {
|
|||
throw new RuntimeException("examId and/or userId cannot be null");
|
||||
}
|
||||
|
||||
return userId + Constants.UNDERLINE + examId;
|
||||
return userId;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -164,7 +159,12 @@ public class TeacherAccountServiceImpl implements TeacherAccountService {
|
|||
public Result<TokenLoginInfo> verifyOneTimeTokenForTeacherAccount(final String loginToken) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
final Claims claims = checkJWTValid(loginToken);
|
||||
final Claims claims;
|
||||
try {
|
||||
claims = checkJWTValid(loginToken);
|
||||
} catch (final Exception e) {
|
||||
throw new UnauthorizedUserException("Invalid One Time JWT", e);
|
||||
}
|
||||
final String userId = claims.get(USER_CLAIM, String.class);
|
||||
|
||||
// check if requested user exists
|
||||
|
@ -174,20 +174,24 @@ public class TeacherAccountServiceImpl implements TeacherAccountService {
|
|||
|
||||
// login the user by getting access token
|
||||
final Map<String, String> params = new HashMap<>();
|
||||
params.put("grant_type", "password");
|
||||
params.put("username", user.username);
|
||||
params.put("password", user.uuid);
|
||||
//final WebAuthenticationDetails details = new WebAuthenticationDetails("localhost", null);
|
||||
params.put(Constants.OAUTH2_GRANT_TYPE, Constants.OAUTH2_GRANT_TYPE_PASSWORD);
|
||||
params.put(Constants.OAUTH2_USER_NAME, user.username);
|
||||
params.put(Constants.OAUTH2_GRANT_TYPE_PASSWORD, claims.get(SUBJECT_CLAIM_NAME, String.class));
|
||||
final UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
this.adminAPIClientDetails, // TODO are this the correct details?
|
||||
null,
|
||||
this.adminAPIClientDetails.getClientId(),
|
||||
"N/A",
|
||||
Collections.emptyList());
|
||||
final ResponseEntity<OAuth2AccessToken> accessToken =
|
||||
this.tokenEndpoint.postAccessToken(usernamePasswordAuthenticationToken, params);
|
||||
final OAuth2AccessToken token = accessToken.getBody();
|
||||
|
||||
return new TokenLoginInfo(user.username, user.uuid, null, token);
|
||||
final String examId = claims.get(EXAM_ID_CLAIM, String.class);
|
||||
final EntityKey redirectTo = (StringUtils.isNotBlank(examId))
|
||||
? new EntityKey(examId, EntityType.EXAM)
|
||||
: null;
|
||||
|
||||
return new TokenLoginInfo(user.username, user.uuid, redirectTo, token);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -225,9 +229,8 @@ public class TeacherAccountServiceImpl implements TeacherAccountService {
|
|||
|
||||
private String createOneTimeToken(final UserInfo account, final Long examId) {
|
||||
|
||||
// create a subject claim for this token only
|
||||
final String subjectClaim = UUID.randomUUID().toString();
|
||||
this.storeSubjectForExam(examId, account.uuid, subjectClaim);
|
||||
userDAO.changePassword(account.uuid, subjectClaim);
|
||||
|
||||
final Map<String, Object> claims = new HashMap<>();
|
||||
claims.put(USER_CLAIM, account.uuid);
|
||||
|
@ -276,38 +279,13 @@ public class TeacherAccountServiceImpl implements TeacherAccountService {
|
|||
final Long examPK = Long.parseLong(examId);
|
||||
|
||||
// check subject
|
||||
final String subjectClaim = getSubjectForExam(examPK, userId);
|
||||
if (StringUtils.isBlank(subjectClaim)) {
|
||||
throw new APIMessage.APIMessageException(APIMessage.ErrorMessage.UNAUTHORIZED.of("Subject not found"));
|
||||
}
|
||||
final String subject = claims.get(SUBJECT_CLAIM_NAME, String.class);
|
||||
if (!subjectClaim.equals(subject)) {
|
||||
if (StringUtils.isBlank(subject)) {
|
||||
throw new APIMessage.APIMessageException(APIMessage.ErrorMessage.UNAUTHORIZED.of("Token subject mismatch"));
|
||||
}
|
||||
return claims;
|
||||
}
|
||||
|
||||
private void storeSubjectForExam(final Long examId, final String userId, final String subject) {
|
||||
additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
examId,
|
||||
EXAM_OTT_SUBJECT_PREFIX + userId,
|
||||
subject)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
private void deleteSubjectForExam(final Long examId, final String userId) {
|
||||
additionalAttributesDAO.delete(EntityType.EXAM, examId, EXAM_OTT_SUBJECT_PREFIX + userId);
|
||||
}
|
||||
|
||||
private String getSubjectForExam(final Long examId, final String userId) {
|
||||
return additionalAttributesDAO
|
||||
.getAdditionalAttribute(EntityType.EXAM, examId, EXAM_OTT_SUBJECT_PREFIX + userId)
|
||||
.map(AdditionalAttributeRecord::getValue)
|
||||
.onError(error -> log.warn("Failed to get OTT subject from exam: {}", error.getMessage()))
|
||||
.getOrElse(null);
|
||||
}
|
||||
|
||||
private UserInfo synchronizeSPSUserForExam(final UserInfo account, final Long examId) {
|
||||
if (this.screenProctoringService.isScreenProctoringEnabled(examId)) {
|
||||
this.screenProctoringService.synchronizeSPSUserForExam(examId);
|
||||
|
|
|
@ -233,7 +233,7 @@ public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam>, BulkActionSup
|
|||
|
||||
@CacheEvict(
|
||||
cacheNames = ExamSessionCacheService.CACHE_NAME_RUNNING_EXAM,
|
||||
key = "#examId")
|
||||
key = "#exam.id")
|
||||
Result<Exam> applySupporter(Exam exam, String userUUID);
|
||||
|
||||
/** This is used by the internal update process to mark exams for which the LMS related data availability
|
||||
|
|
|
@ -552,7 +552,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
|||
IOUtils.closeQuietly(out);
|
||||
}
|
||||
})
|
||||
.onError(error -> log.error("Failed to apply ConnectionConfiguration for exam: {} error: {}", exam, error.getMessage()))
|
||||
.onError(error -> log.error("Failed to apply ConnectionConfiguration for exam: {} error: ", exam, error))
|
||||
.getOr(exam);
|
||||
}
|
||||
|
||||
|
|
|
@ -379,7 +379,7 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
|
|||
multiPartAttributes.add("token", this.accessToken);
|
||||
|
||||
return super.postForObject(
|
||||
uploadEndpoint,
|
||||
uri,
|
||||
multiPartAttributes,
|
||||
String.class);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import java.util.List;
|
|||
import java.util.concurrent.CompletionException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.NoResourceFoundException;
|
||||
import org.apache.catalina.connector.ClientAbortException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -61,7 +62,7 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler {
|
|||
((OnlyMessageLogExceptionWrapper) ex).log(log);
|
||||
return new ResponseEntity<>(status);
|
||||
} else {
|
||||
log.error("Unexpected generic error catched at the API endpoint: ", ex);
|
||||
log.error("Unexpected generic error caught at the API endpoint: ", ex);
|
||||
}
|
||||
|
||||
final List<APIMessage> errors = Arrays.asList(APIMessage.ErrorMessage.GENERIC.of(ex.getMessage()));
|
||||
|
@ -160,6 +161,15 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler {
|
|||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@ExceptionHandler(NoResourceFoundException.class)
|
||||
public ResponseEntity<Object> handleNoResourceFoundException(
|
||||
final NoResourceFoundException ex,
|
||||
final WebRequest request) {
|
||||
|
||||
return APIMessage.ErrorMessage.RESOURCE_NOT_FOUND
|
||||
.createErrorResponse(ex.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(ResourceNotFoundException.class)
|
||||
public ResponseEntity<Object> handleResourceNotFoundException(
|
||||
final ResourceNotFoundException ex,
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (c) 2019 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
||||
|
||||
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.TooManyRequests;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.TokenLoginInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.TeacherAccountService;
|
||||
import io.github.bucket4j.local.LocalBucket;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
public class AdminJWTAccess {
|
||||
|
||||
private final TeacherAccountService teacherAccountService;
|
||||
private final LocalBucket requestRateLimitBucket;
|
||||
|
||||
public AdminJWTAccess(
|
||||
final TeacherAccountService teacherAccountService,
|
||||
final RateLimitService rateLimitService) {
|
||||
this.teacherAccountService = teacherAccountService;
|
||||
this.requestRateLimitBucket = rateLimitService.createRequestLimitBucker();
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
path = API.OAUTH_JWT_TOKEN_VERIFY_ENDPOINT,
|
||||
method = RequestMethod.POST,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public TokenLoginInfo verifyJWTToken(@RequestHeader(name = "ONE_TIME_TOKEN_TO_VERIFY") final String loginToken) {
|
||||
|
||||
if (!this.requestRateLimitBucket.tryConsume(1)) {
|
||||
throw new TooManyRequests();
|
||||
}
|
||||
|
||||
final Result<TokenLoginInfo> tokenLoginInfoResult = teacherAccountService
|
||||
.verifyOneTimeTokenForTeacherAccount(loginToken);
|
||||
|
||||
if (tokenLoginInfoResult.hasError()) {
|
||||
throw new APIMessage.APIMessageException(
|
||||
APIMessage.ErrorMessage.UNAUTHORIZED.of(tokenLoginInfoResult.getError()));
|
||||
}
|
||||
|
||||
return tokenLoginInfoResult.get();
|
||||
}
|
||||
}
|
|
@ -192,8 +192,7 @@ public class ClientConnectionController extends ReadonlyEntityController<ClientC
|
|||
this.authorization.checkRole(
|
||||
institution,
|
||||
EntityType.CLIENT_EVENT,
|
||||
UserRole.EXAM_ADMIN,
|
||||
UserRole.EXAM_SUPPORTER);
|
||||
UserRole.EXAM_ADMIN, UserRole.EXAM_SUPPORTER, UserRole.TEACHER);
|
||||
}
|
||||
|
||||
private Result<Collection<ClientConnectionData>> getAllData(final FilterMap filterMap) {
|
||||
|
|
|
@ -221,7 +221,9 @@ public class ClientEventController extends ReadonlyEntityController<ClientEvent,
|
|||
.getUserService()
|
||||
.getCurrentUser()
|
||||
.getUserRoles();
|
||||
final boolean isSupporterOnly = userRoles.size() == 1 && userRoles.contains(UserRole.EXAM_SUPPORTER);
|
||||
final boolean isSupporterOnly = userRoles.size() == 1 &&
|
||||
(userRoles.contains(UserRole.EXAM_SUPPORTER) || userRoles.contains(UserRole.TEACHER));
|
||||
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
|
|
|
@ -169,7 +169,7 @@ public class ExamMonitoringController {
|
|||
this.authorization.checkRole(
|
||||
institutionId,
|
||||
EntityType.EXAM,
|
||||
UserRole.EXAM_SUPPORTER,
|
||||
UserRole.EXAM_SUPPORTER, UserRole.TEACHER,
|
||||
UserRole.EXAM_ADMIN);
|
||||
|
||||
final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString());
|
||||
|
@ -230,7 +230,7 @@ public class ExamMonitoringController {
|
|||
this.authorization.checkRole(
|
||||
institutionId,
|
||||
EntityType.EXAM,
|
||||
UserRole.EXAM_SUPPORTER,
|
||||
UserRole.EXAM_SUPPORTER, UserRole.TEACHER,
|
||||
UserRole.EXAM_ADMIN);
|
||||
|
||||
final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString());
|
||||
|
@ -511,7 +511,7 @@ public class ExamMonitoringController {
|
|||
this.authorization.checkRole(
|
||||
institutionId,
|
||||
EntityType.EXAM,
|
||||
UserRole.EXAM_SUPPORTER,
|
||||
UserRole.EXAM_SUPPORTER, UserRole.TEACHER,
|
||||
UserRole.EXAM_ADMIN);
|
||||
|
||||
// check exam running
|
||||
|
|
|
@ -90,8 +90,8 @@ public class LmsIntegrationController {
|
|||
|
||||
final EntityKey examID = fullLmsIntegrationService.deleteExam(lmsUUId, courseId, quizId)
|
||||
.onError(e -> log.error(
|
||||
"Failed to delete exam: lmsId:{}, courseId: {}, quizId: {}",
|
||||
lmsUUId, courseId, quizId, e))
|
||||
"Failed to delete exam: lmsId:{}, courseId: {}, quizId: {}, error: {}",
|
||||
lmsUUId, courseId, quizId, e.getMessage()))
|
||||
.getOrThrow();
|
||||
|
||||
log.info("Auto delete of exam successful: {}", examID);
|
||||
|
@ -141,8 +141,7 @@ public class LmsIntegrationController {
|
|||
@RequestMapping(
|
||||
path = API.LMS_FULL_INTEGRATION_LOGIN_TOKEN_ENDPOINT,
|
||||
method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
public FullLmsIntegrationService.TokenLoginResponse getOneTimeLoginToken(
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_LMS_UUID) final String lmsUUId,
|
||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_COURSE_ID) final String courseId,
|
||||
|
@ -166,7 +165,7 @@ public class LmsIntegrationController {
|
|||
|
||||
final String token = this.fullLmsIntegrationService
|
||||
.getOneTimeLoginToken(lmsUUId, courseId, quizId, adHocAccountData)
|
||||
.onError(error -> log.error("Failed to create ad-hoc account with one time login token: ", error))
|
||||
.onError(error -> log.error("Failed to create ad-hoc account with one time login token, error: {}", error.getMessage()))
|
||||
.getOrThrow();
|
||||
|
||||
return new FullLmsIntegrationService.TokenLoginResponse(
|
||||
|
|
|
@ -68,7 +68,7 @@ public class RegisterUserController {
|
|||
this.userActivityLogDAO = userActivityLogDAO;
|
||||
this.userDAO = userDAO;
|
||||
this.beanValidationService = beanValidationService;
|
||||
this. registeringEnabled = BooleanUtils.isTrue(features.get(UserFeatures.Feature.ADMIN_USER_ACCOUNT_SELF_REGISTERING.featureName));
|
||||
this.registeringEnabled = BooleanUtils.isTrue(features.get(UserFeatures.Feature.ADMIN_USER_ACCOUNT_SELF_REGISTERING.featureName));
|
||||
this.autoActivation = BooleanUtils.isTrue(features.get(UserFeatures.Feature.ADMIN_USER_ACCOUNT_SELF_REGISTERING_AUTO_ACTIVATION.featureName));
|
||||
this.requestRateLimitBucket = rateLimitService.createRequestLimitBucker();
|
||||
this.createRateLimitBucket = rateLimitService.createCreationLimitBucker();
|
||||
|
|
|
@ -17,12 +17,14 @@ import java.util.Collection;
|
|||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConnectionConfigurationChangeEvent;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.mybatis.dynamic.sql.SqlTable;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
|
@ -62,6 +64,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
|
|||
public class SEBClientConfigController extends ActivatableEntityController<SEBClientConfig, SEBClientConfig> {
|
||||
|
||||
private final ConnectionConfigurationService sebConnectionConfigurationService;
|
||||
private final ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
public SEBClientConfigController(
|
||||
final SEBClientConfigDAO sebClientConfigDAO,
|
||||
|
@ -70,7 +73,8 @@ public class SEBClientConfigController extends ActivatableEntityController<SEBCl
|
|||
final BulkActionService bulkActionService,
|
||||
final PaginationService paginationService,
|
||||
final BeanValidationService beanValidationService,
|
||||
final ConnectionConfigurationService sebConnectionConfigurationService) {
|
||||
final ConnectionConfigurationService sebConnectionConfigurationService,
|
||||
final ApplicationEventPublisher applicationEventPublisher) {
|
||||
|
||||
super(authorization,
|
||||
bulkActionService,
|
||||
|
@ -80,6 +84,7 @@ public class SEBClientConfigController extends ActivatableEntityController<SEBCl
|
|||
beanValidationService);
|
||||
|
||||
this.sebConnectionConfigurationService = sebConnectionConfigurationService;
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
|
@ -183,6 +188,10 @@ public class SEBClientConfigController extends ActivatableEntityController<SEBCl
|
|||
if (entity.isActive()) {
|
||||
// try to get access token for SEB client
|
||||
this.sebConnectionConfigurationService.initialCheckAccess(entity);
|
||||
// notify all
|
||||
applicationEventPublisher.publishEvent(new ConnectionConfigurationChangeEvent(
|
||||
entity.institutionId,
|
||||
entity.id));
|
||||
}
|
||||
return super.notifySaved(entity);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.weblayer.oauth;
|
|||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -19,6 +20,8 @@ import org.springframework.context.annotation.Configuration;
|
|||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
|
||||
|
@ -113,4 +116,6 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
|
|||
.tokenServices(defaultTokenServices);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue