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_INTEGER = "integer";
|
||||||
public static final String XML_PLIST_REAL = "real";
|
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_GRANT_TYPE_PASSWORD = "password";
|
||||||
public static final String OAUTH2_CLIENT_SECRET = "client_secret";
|
public static final String OAUTH2_CLIENT_SECRET = "client_secret";
|
||||||
public static final String OAUTH2_GRANT_TYPE_REFRESH_TOKEN = "refresh_token";
|
public static final String OAUTH2_GRANT_TYPE_REFRESH_TOKEN = "refresh_token";
|
||||||
|
|
|
@ -8,26 +8,27 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.gbl.model.user;
|
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.JsonCreator;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||||
|
|
||||||
public class TokenLoginInfo {
|
public class TokenLoginInfo {
|
||||||
@JsonProperty
|
@JsonProperty("username")
|
||||||
public final String username;
|
public final String username;
|
||||||
@JsonProperty
|
@JsonProperty("userUUID")
|
||||||
public final String userUUID;
|
public final String userUUID;
|
||||||
@JsonProperty
|
@JsonProperty("redirect")
|
||||||
public final String redirect;
|
public final EntityKey redirect;
|
||||||
@JsonProperty
|
@JsonProperty("login")
|
||||||
public final OAuth2AccessToken login;
|
public final OAuth2AccessToken login;
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
public TokenLoginInfo(
|
public TokenLoginInfo(
|
||||||
@JsonProperty final String username,
|
@JsonProperty("username") final String username,
|
||||||
@JsonProperty final String userUUID,
|
@JsonProperty("userUUID") final String userUUID,
|
||||||
@JsonProperty final String redirect,
|
@JsonProperty("redirect") final EntityKey redirect,
|
||||||
@JsonProperty final OAuth2AccessToken login) {
|
@JsonProperty("login") final OAuth2AccessToken login) {
|
||||||
|
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.userUUID = userUUID;
|
this.userUUID = userUUID;
|
||||||
|
|
|
@ -50,7 +50,7 @@ public enum UserRole implements Entity, GrantedAuthority {
|
||||||
public static List<UserRole> publicRolesForUser(final UserInfo user) {
|
public static List<UserRole> publicRolesForUser(final UserInfo user) {
|
||||||
final EnumSet<UserRole> roles = user.getUserRoles();
|
final EnumSet<UserRole> roles = user.getUserRoles();
|
||||||
if (roles.contains(SEB_SERVER_ADMIN)) {
|
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)) {
|
} else if (roles.contains(INSTITUTIONAL_ADMIN)) {
|
||||||
return Arrays.asList(INSTITUTIONAL_ADMIN, EXAM_ADMIN, EXAM_SUPPORTER);
|
return Arrays.asList(INSTITUTIONAL_ADMIN, EXAM_ADMIN, EXAM_SUPPORTER);
|
||||||
} else if (roles.contains(EXAM_ADMIN)) {
|
} 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.LOGO_PATH_SEGMENT + "/**").permitAll()
|
||||||
.antMatchers(adminAPIEndpoint + API.INFO_ENDPOINT + API.INFO_INST_PATH_SEGMENT + "/**").permitAll()
|
.antMatchers(adminAPIEndpoint + API.INFO_ENDPOINT + API.INFO_INST_PATH_SEGMENT + "/**").permitAll()
|
||||||
.antMatchers(adminAPIEndpoint + API.REGISTER_ENDPOINT).permitAll()
|
.antMatchers(adminAPIEndpoint + API.REGISTER_ENDPOINT).permitAll()
|
||||||
|
.antMatchers(API.OAUTH_JWT_TOKEN_ENDPOINT + "/**").permitAll()
|
||||||
.and()
|
.and()
|
||||||
.antMatcher("/**")
|
.antMatcher("/**")
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
|
|
|
@ -21,6 +21,8 @@ import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.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.codec.binary.Base64InputStream;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.eclipse.rap.rwt.RWT;
|
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.gbl.profile.GuiProfile;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.WebserviceURIService;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.WebserviceURIService;
|
||||||
import ch.ethz.seb.sebserver.gui.widget.ImageUploadSelection;
|
import ch.ethz.seb.sebserver.gui.widget.ImageUploadSelection;
|
||||||
|
import org.springframework.web.context.WebApplicationContext;
|
||||||
|
import org.springframework.web.context.support.WebApplicationContextUtils;
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
@Component
|
@Component
|
||||||
|
@ -116,6 +120,20 @@ public final class InstitutionalAuthenticationEntryPoint implements Authenticati
|
||||||
final HttpServletResponse response,
|
final HttpServletResponse response,
|
||||||
final AuthenticationException authException) throws IOException, ServletException {
|
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);
|
final String institutionalEndpoint = extractInstitutionalEndpoint(request);
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(institutionalEndpoint)) {
|
if (StringUtils.isNotBlank(institutionalEndpoint)) {
|
||||||
|
@ -168,7 +186,6 @@ public final class InstitutionalAuthenticationEntryPoint implements Authenticati
|
||||||
request.getSession().removeAttribute(API.PARAM_LOGO_IMAGE);
|
request.getSession().removeAttribute(API.PARAM_LOGO_IMAGE);
|
||||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
forwardToEntryPoint(request, response, this.guiEntryPoint, institutionalEndpoint == null);
|
forwardToEntryPoint(request, response, this.guiEntryPoint, institutionalEndpoint == null);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void forwardToEntryPoint(
|
private void forwardToEntryPoint(
|
||||||
|
|
|
@ -93,103 +93,105 @@ public class ActivitiesPane implements TemplateComposer {
|
||||||
|
|
||||||
final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(pageContext);
|
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 ----------------------------------------------------------------------
|
// ---- SEB ADMIN ----------------------------------------------------------------------
|
||||||
|
|
||||||
final boolean isServerOrInstAdmin = this.currentUser.get()
|
final boolean isServerOrInstAdmin = this.currentUser.get()
|
||||||
.hasAnyRole(UserRole.SEB_SERVER_ADMIN, UserRole.INSTITUTIONAL_ADMIN);
|
.hasAnyRole(UserRole.SEB_SERVER_ADMIN, UserRole.INSTITUTIONAL_ADMIN);
|
||||||
|
|
||||||
// SEB Server Administration
|
if (isServerOrInstAdmin) {
|
||||||
final TreeItem sebAdmin = this.widgetFactory.treeItemLocalized(
|
// SEB Server Administration
|
||||||
navigation,
|
final TreeItem sebAdmin = this.widgetFactory.treeItemLocalized(
|
||||||
ActivityDefinition.SEB_ADMINISTRATION.displayName);
|
navigation,
|
||||||
|
ActivityDefinition.SEB_ADMINISTRATION.displayName);
|
||||||
|
|
||||||
// Institution
|
// Institution
|
||||||
|
|
||||||
if (currentUser.isFeatureEnabled(UserFeatures.Feature.ADMIN_INSTITUTION)) {
|
if (currentUser.isFeatureEnabled(UserFeatures.Feature.ADMIN_INSTITUTION)) {
|
||||||
// If current user has SEB Server Admin role, show the Institution list
|
// If current user has SEB Server Admin role, show the Institution list
|
||||||
if (userInfo.hasRole(UserRole.SEB_SERVER_ADMIN)) {
|
if (userInfo.hasRole(UserRole.SEB_SERVER_ADMIN)) {
|
||||||
// institutions (list) as root
|
// institutions (list) as root
|
||||||
final TreeItem institutions = this.widgetFactory.treeItemLocalized(
|
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,
|
sebAdmin,
|
||||||
ActivityDefinition.INSTITUTION.displayName);
|
ActivityDefinition.USER_ACCOUNT.displayName);
|
||||||
injectActivitySelection(
|
injectActivitySelection(
|
||||||
institutions,
|
userAccounts,
|
||||||
actionBuilder
|
actionBuilder
|
||||||
.newAction(ActionDefinition.INSTITUTION_VIEW_LIST)
|
.newAction(ActionDefinition.USER_ACCOUNT_VIEW_LIST)
|
||||||
.create());
|
.create());
|
||||||
|
} else if (currentUser.isFeatureEnabled(UserFeatures.Feature.ADMIN_USER_ACCOUNT)) {
|
||||||
} else if (userInfo.hasRole(UserRole.INSTITUTIONAL_ADMIN)) {
|
// otherwise show the user account form for current user
|
||||||
// otherwise show the form of the institution for current user
|
final TreeItem userAccounts = pageService.isLightSetup() || !currentUser.isFeatureEnabled(UserFeatures.Feature.ADMIN_USER_ADMINISTRATION)
|
||||||
final TreeItem institutions = this.widgetFactory.treeItemLocalized(
|
? this.widgetFactory.treeItemLocalized(
|
||||||
sebAdmin,
|
sebAdmin,
|
||||||
ActivityDefinition.INSTITUTION.displayName);
|
ActivityDefinition.USER_ACCOUNT.displayName)
|
||||||
|
: this.widgetFactory.treeItemLocalized(
|
||||||
|
navigation,
|
||||||
|
ActivityDefinition.USER_ACCOUNT.displayName);
|
||||||
injectActivitySelection(
|
injectActivitySelection(
|
||||||
institutions,
|
userAccounts,
|
||||||
actionBuilder.newAction(ActionDefinition.INSTITUTION_VIEW_FORM)
|
actionBuilder.newAction(ActionDefinition.USER_ACCOUNT_VIEW_FORM)
|
||||||
.withEntityKey(userInfo.institutionId, EntityType.INSTITUTION)
|
.withEntityKey(this.currentUser.get().getEntityKey())
|
||||||
.withAttribute(AttributeKeys.READ_ONLY, "true")
|
.withAttribute(AttributeKeys.READ_ONLY, "true")
|
||||||
.create());
|
.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 ----------------------------------------------------------------------
|
// ---- SEB ADMIN ----------------------------------------------------------------------
|
||||||
//--------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -252,7 +254,7 @@ public class ActivitiesPane implements TemplateComposer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Certificate management
|
// Certificate management
|
||||||
if (!isSupporterOnly && certificatesEnabled) {
|
if (certificatesEnabled) {
|
||||||
final TreeItem examConfigTemplate = this.widgetFactory.treeItemLocalized(
|
final TreeItem examConfigTemplate = this.widgetFactory.treeItemLocalized(
|
||||||
sebConfigs,
|
sebConfigs,
|
||||||
ActivityDefinition.SEB_CERTIFICATE_MANAGEMENT.displayName);
|
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 examTemplateEnabled = currentUser.isFeatureEnabled(UserFeatures.Feature.EXAM_TEMPLATE);
|
||||||
final boolean anyExamAdminEnabled = lmsSetupEnabled || quizLookupEnabled || examEnabled || examTemplateEnabled;
|
final boolean anyExamAdminEnabled = lmsSetupEnabled || quizLookupEnabled || examEnabled || examTemplateEnabled;
|
||||||
|
|
||||||
if (anyExamAdminEnabled) {
|
if (anyExamAdminEnabled && !isTeacherOnly) {
|
||||||
// Exam Administration
|
// Exam Administration
|
||||||
final TreeItem examAdmin = this.widgetFactory.treeItemLocalized(
|
final TreeItem examAdmin = this.widgetFactory.treeItemLocalized(
|
||||||
navigation,
|
navigation,
|
||||||
|
@ -369,7 +371,7 @@ public class ActivitiesPane implements TemplateComposer {
|
||||||
ActivityDefinition.MONITORING.displayName);
|
ActivityDefinition.MONITORING.displayName);
|
||||||
|
|
||||||
// Monitoring exams
|
// Monitoring exams
|
||||||
if (isSupporter) {
|
if (isSupporter || isTeacherOnly) {
|
||||||
|
|
||||||
if (monitoringEnabled) {
|
if (monitoringEnabled) {
|
||||||
final TreeItem monitoringExams = this.widgetFactory.treeItemLocalized(
|
final TreeItem monitoringExams = this.widgetFactory.treeItemLocalized(
|
||||||
|
@ -382,7 +384,7 @@ public class ActivitiesPane implements TemplateComposer {
|
||||||
.create());
|
.create());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (finishedEnabled) {
|
if (finishedEnabled && !isTeacherOnly) {
|
||||||
final TreeItem finishedExams = this.widgetFactory.treeItemLocalized(
|
final TreeItem finishedExams = this.widgetFactory.treeItemLocalized(
|
||||||
monitoring,
|
monitoring,
|
||||||
ActivityDefinition.FINISHED_EXAMS.displayName);
|
ActivityDefinition.FINISHED_EXAMS.displayName);
|
||||||
|
@ -395,7 +397,7 @@ public class ActivitiesPane implements TemplateComposer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SEB Client Logs
|
// SEB Client Logs
|
||||||
if (viewSEBClientLogs) {
|
if (viewSEBClientLogs && !isTeacherOnly) {
|
||||||
final TreeItem sebLogs = (isSupporter)
|
final TreeItem sebLogs = (isSupporter)
|
||||||
? this.widgetFactory.treeItemLocalized(
|
? this.widgetFactory.treeItemLocalized(
|
||||||
monitoring,
|
monitoring,
|
||||||
|
@ -414,7 +416,7 @@ public class ActivitiesPane implements TemplateComposer {
|
||||||
monitoring.setExpanded(
|
monitoring.setExpanded(
|
||||||
this.currentUser
|
this.currentUser
|
||||||
.get()
|
.get()
|
||||||
.hasAnyRole(UserRole.EXAM_SUPPORTER));
|
.hasAnyRole(UserRole.EXAM_SUPPORTER, UserRole.TEACHER));
|
||||||
} else {
|
} else {
|
||||||
monitoring.dispose();
|
monitoring.dispose();
|
||||||
}
|
}
|
||||||
|
@ -486,7 +488,7 @@ public class ActivitiesPane implements TemplateComposer {
|
||||||
return findItemByActionDefinition(
|
return findItemByActionDefinition(
|
||||||
navigation.getItems(),
|
navigation.getItems(),
|
||||||
ActivityDefinition.SEB_EXAM_CONFIG);
|
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(
|
return findItemByActionDefinition(
|
||||||
navigation.getItems(),
|
navigation.getItems(),
|
||||||
ActivityDefinition.MONITORING_EXAMS);
|
ActivityDefinition.MONITORING_EXAMS);
|
||||||
|
|
|
@ -95,6 +95,9 @@ public class FinishedExamList implements TemplateComposer {
|
||||||
final RestService restService = this.resourceService.getRestService();
|
final RestService restService = this.resourceService.getRestService();
|
||||||
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
|
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
|
||||||
|
|
||||||
|
boolean roleBasedAccess = currentUser.get()
|
||||||
|
.hasAnyRole(UserRole.EXAM_SUPPORTER);
|
||||||
|
|
||||||
// content page layout with title
|
// content page layout with title
|
||||||
final Composite content = widgetFactory.defaultPageLayout(
|
final Composite content = widgetFactory.defaultPageLayout(
|
||||||
pageContext.getParent(),
|
pageContext.getParent(),
|
||||||
|
@ -165,7 +168,7 @@ public class FinishedExamList implements TemplateComposer {
|
||||||
table::getMultiSelection,
|
table::getMultiSelection,
|
||||||
PageAction::applySingleSelectionAsEntityKey,
|
PageAction::applySingleSelectionAsEntityKey,
|
||||||
EMPTY_SELECTION_TEXT_KEY)
|
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))
|
.onError(error -> pageContext.notifyLoadError(EntityType.EXAM, error))
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
final UserInfo user = currentUser.get();
|
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);
|
exam.supporter.contains(user.uuid);
|
||||||
final BooleanSupplier isExamSupporter = () -> supporting || user.hasRole(UserRole.EXAM_ADMIN);
|
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 quitEnabled = currentUser.isFeatureEnabled(MONITORING_RUNNING_EXAM_QUIT);
|
||||||
final boolean lockscreenEnabled = 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 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);
|
exam.supporter.contains(user.uuid);
|
||||||
final BooleanSupplier isExamSupporter = () -> supporting || user.hasRole(UserRole.EXAM_ADMIN);
|
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 RestService restService = this.resourceService.getRestService();
|
||||||
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
|
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
|
||||||
|
|
||||||
|
boolean hasRoleBasedAccess = currentUser.get().hasAnyRole(
|
||||||
|
UserRole.EXAM_SUPPORTER, UserRole.TEACHER);
|
||||||
|
|
||||||
// content page layout with title
|
// content page layout with title
|
||||||
final Composite content = widgetFactory.defaultPageLayout(
|
final Composite content = widgetFactory.defaultPageLayout(
|
||||||
pageContext.getParent(),
|
pageContext.getParent(),
|
||||||
|
@ -149,7 +152,7 @@ public class MonitoringRunningExamList implements TemplateComposer {
|
||||||
table::getMultiSelection,
|
table::getMultiSelection,
|
||||||
PageAction::applySingleSelectionAsEntityKey,
|
PageAction::applySingleSelectionAsEntityKey,
|
||||||
EMPTY_SELECTION_TEXT_KEY)
|
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.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
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.BooleanUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
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.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.*;
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||||
import org.springframework.http.client.ClientHttpResponse;
|
import org.springframework.http.client.ClientHttpResponse;
|
||||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||||
|
@ -146,12 +142,14 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol
|
||||||
|
|
||||||
private boolean valid = true;
|
private boolean valid = true;
|
||||||
|
|
||||||
|
private final ClientHttpRequestFactory clientHttpRequestFactory;
|
||||||
private final ResourceOwnerPasswordResourceDetails resource;
|
private final ResourceOwnerPasswordResourceDetails resource;
|
||||||
private final DisposableOAuth2RestTemplate restTemplate;
|
private final DisposableOAuth2RestTemplate restTemplate;
|
||||||
private final String revokeTokenURI;
|
private final String revokeTokenURI;
|
||||||
private final String currentUserURI;
|
private final String currentUserURI;
|
||||||
private final String loginLogURI;
|
private final String loginLogURI;
|
||||||
private final String logoutLogURI;
|
private final String logoutLogURI;
|
||||||
|
private final String jwtTokenVerificationURI;
|
||||||
|
|
||||||
private Result<UserInfo> loggedInUser = null;
|
private Result<UserInfo> loggedInUser = null;
|
||||||
|
|
||||||
|
@ -161,6 +159,7 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol
|
||||||
final WebserviceURIService webserviceURIService,
|
final WebserviceURIService webserviceURIService,
|
||||||
final ClientHttpRequestFactory clientHttpRequestFactory) {
|
final ClientHttpRequestFactory clientHttpRequestFactory) {
|
||||||
|
|
||||||
|
this.clientHttpRequestFactory = clientHttpRequestFactory;
|
||||||
this.resource = new ResourceOwnerPasswordResourceDetails();
|
this.resource = new ResourceOwnerPasswordResourceDetails();
|
||||||
this.resource.setAccessTokenUri(webserviceURIService.getOAuthTokenURI());
|
this.resource.setAccessTokenUri(webserviceURIService.getOAuthTokenURI());
|
||||||
this.resource.setClientId(guiClientId);
|
this.resource.setClientId(guiClientId);
|
||||||
|
@ -179,6 +178,7 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol
|
||||||
this.currentUserURI = webserviceURIService.getCurrentUserRequestURI();
|
this.currentUserURI = webserviceURIService.getCurrentUserRequestURI();
|
||||||
this.loginLogURI = webserviceURIService.getLoginLogPostURI();
|
this.loginLogURI = webserviceURIService.getLoginLogPostURI();
|
||||||
this.logoutLogURI = webserviceURIService.getLogoutLogPostURI();
|
this.logoutLogURI = webserviceURIService.getLogoutLogPostURI();
|
||||||
|
this.jwtTokenVerificationURI = webserviceURIService.getJWTTokenVerificationURI();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -193,9 +193,6 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO check if this is needed. If not remove it.
|
|
||||||
// This gets called many times for a page load
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final ResponseEntity<String> forEntity =
|
final ResponseEntity<String> forEntity =
|
||||||
this.restTemplate.getForEntity(this.currentUserURI, String.class);
|
this.restTemplate.getForEntity(this.currentUserURI, String.class);
|
||||||
|
@ -264,8 +261,32 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean autoLogin(final String oneTimeToken) {
|
public boolean autoLogin(final String oneTimeToken) {
|
||||||
// TODO call auto-login API on Webservice to verify the oneTimeToken and
|
try {
|
||||||
return false;
|
// 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
|
@Override
|
||||||
|
|
|
@ -83,4 +83,11 @@ public class WebserviceURIService {
|
||||||
.path(API.USER_ACCOUNT_ENDPOINT + API.LOGOUT_PATH_SEGMENT)
|
.path(API.USER_ACCOUNT_ENDPOINT + API.LOGOUT_PATH_SEGMENT)
|
||||||
.toUriString();
|
.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;
|
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.exam.Exam;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.TokenLoginInfo;
|
import ch.ethz.seb.sebserver.gbl.model.user.TokenLoginInfo;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
|
||||||
|
|
||||||
|
/** Service used to maintain Teacher Ad-Hoc Accounts */
|
||||||
public interface TeacherAccountService {
|
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(
|
default String getTeacherAccountIdentifier(
|
||||||
final Exam exam,
|
final Exam exam,
|
||||||
final FullLmsIntegrationService.AdHocAccountData adHocAccountData) {
|
final FullLmsIntegrationService.AdHocAccountData adHocAccountData) {
|
||||||
return getTeacherAccountIdentifier(exam.getModelId(), adHocAccountData.userId);
|
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);
|
String getTeacherAccountIdentifier(String examId, String userId);
|
||||||
|
|
||||||
Result<UserInfo> createNewTeacherAccountForExam(
|
/** Deactivates a certain ad-hoc Teacher account
|
||||||
Exam exam,
|
* Usually called when an exam is deleted. Checks if Teacher account for exam
|
||||||
final FullLmsIntegrationService.AdHocAccountData adHocAccountData);
|
* 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);
|
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(
|
Result<String> getOneTimeTokenForTeacherAccount(
|
||||||
Exam exam,
|
Exam exam,
|
||||||
FullLmsIntegrationService.AdHocAccountData adHocAccountData,
|
FullLmsIntegrationService.AdHocAccountData adHocAccountData,
|
||||||
boolean createIfNotExists);
|
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);
|
Result<TokenLoginInfo> verifyOneTimeTokenForTeacherAccount(String token);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,6 +123,9 @@ public class AuthorizationServiceImpl implements AuthorizationService {
|
||||||
.andForRole(UserRole.EXAM_SUPPORTER)
|
.andForRole(UserRole.EXAM_SUPPORTER)
|
||||||
.withInstitutionalPrivilege(PrivilegeType.ASSIGNED)
|
.withInstitutionalPrivilege(PrivilegeType.ASSIGNED)
|
||||||
.withOwnerPrivilege(PrivilegeType.MODIFY)
|
.withOwnerPrivilege(PrivilegeType.MODIFY)
|
||||||
|
.andForRole(UserRole.TEACHER)
|
||||||
|
.withInstitutionalPrivilege(PrivilegeType.ASSIGNED)
|
||||||
|
.withOwnerPrivilege(PrivilegeType.READ)
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
// grants for exam templates
|
// grants for exam templates
|
||||||
|
|
|
@ -13,6 +13,7 @@ import java.util.*;
|
||||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
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.TokenLoginInfo;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
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.Cryptor;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
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.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.ExamDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
|
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.BadCredentialsException;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
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.security.oauth2.provider.endpoint.TokenEndpoint;
|
||||||
import org.springframework.stereotype.Service;
|
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 USER_CLAIM = "usr";
|
||||||
private static final String EXAM_ID_CLAIM = "exam";
|
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 UserDAO userDAO;
|
||||||
private final ScreenProctoringService screenProctoringService;
|
private final ScreenProctoringService screenProctoringService;
|
||||||
private final ExamDAO examDAO;
|
private final ExamDAO examDAO;
|
||||||
private final Cryptor cryptor;
|
private final Cryptor cryptor;
|
||||||
private final AdditionalAttributesDAO additionalAttributesDAO;
|
|
||||||
final TokenEndpoint tokenEndpoint;
|
final TokenEndpoint tokenEndpoint;
|
||||||
private final AdminAPIClientDetails adminAPIClientDetails;
|
private final AdminAPIClientDetails adminAPIClientDetails;
|
||||||
|
|
||||||
|
@ -71,7 +68,6 @@ public class TeacherAccountServiceImpl implements TeacherAccountService {
|
||||||
final ScreenProctoringService screenProctoringService,
|
final ScreenProctoringService screenProctoringService,
|
||||||
final ExamDAO examDAO,
|
final ExamDAO examDAO,
|
||||||
final Cryptor cryptor,
|
final Cryptor cryptor,
|
||||||
final AdditionalAttributesDAO additionalAttributesDAO,
|
|
||||||
final TokenEndpoint tokenEndpoint,
|
final TokenEndpoint tokenEndpoint,
|
||||||
final AdminAPIClientDetails adminAPIClientDetails) {
|
final AdminAPIClientDetails adminAPIClientDetails) {
|
||||||
|
|
||||||
|
@ -79,7 +75,6 @@ public class TeacherAccountServiceImpl implements TeacherAccountService {
|
||||||
this.screenProctoringService = screenProctoringService;
|
this.screenProctoringService = screenProctoringService;
|
||||||
this.examDAO = examDAO;
|
this.examDAO = examDAO;
|
||||||
this.cryptor = cryptor;
|
this.cryptor = cryptor;
|
||||||
this.additionalAttributesDAO = additionalAttributesDAO;
|
|
||||||
this.tokenEndpoint = tokenEndpoint;
|
this.tokenEndpoint = tokenEndpoint;
|
||||||
this.adminAPIClientDetails = adminAPIClientDetails;
|
this.adminAPIClientDetails = adminAPIClientDetails;
|
||||||
}
|
}
|
||||||
|
@ -90,7 +85,7 @@ public class TeacherAccountServiceImpl implements TeacherAccountService {
|
||||||
throw new RuntimeException("examId and/or userId cannot be null");
|
throw new RuntimeException("examId and/or userId cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
return userId + Constants.UNDERLINE + examId;
|
return userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -164,7 +159,12 @@ public class TeacherAccountServiceImpl implements TeacherAccountService {
|
||||||
public Result<TokenLoginInfo> verifyOneTimeTokenForTeacherAccount(final String loginToken) {
|
public Result<TokenLoginInfo> verifyOneTimeTokenForTeacherAccount(final String loginToken) {
|
||||||
return Result.tryCatch(() -> {
|
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);
|
final String userId = claims.get(USER_CLAIM, String.class);
|
||||||
|
|
||||||
// check if requested user exists
|
// check if requested user exists
|
||||||
|
@ -174,20 +174,24 @@ public class TeacherAccountServiceImpl implements TeacherAccountService {
|
||||||
|
|
||||||
// login the user by getting access token
|
// login the user by getting access token
|
||||||
final Map<String, String> params = new HashMap<>();
|
final Map<String, String> params = new HashMap<>();
|
||||||
params.put("grant_type", "password");
|
params.put(Constants.OAUTH2_GRANT_TYPE, Constants.OAUTH2_GRANT_TYPE_PASSWORD);
|
||||||
params.put("username", user.username);
|
params.put(Constants.OAUTH2_USER_NAME, user.username);
|
||||||
params.put("password", user.uuid);
|
params.put(Constants.OAUTH2_GRANT_TYPE_PASSWORD, claims.get(SUBJECT_CLAIM_NAME, String.class));
|
||||||
//final WebAuthenticationDetails details = new WebAuthenticationDetails("localhost", null);
|
|
||||||
final UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
|
final UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
|
||||||
new UsernamePasswordAuthenticationToken(
|
new UsernamePasswordAuthenticationToken(
|
||||||
this.adminAPIClientDetails, // TODO are this the correct details?
|
this.adminAPIClientDetails.getClientId(),
|
||||||
null,
|
"N/A",
|
||||||
Collections.emptyList());
|
Collections.emptyList());
|
||||||
final ResponseEntity<OAuth2AccessToken> accessToken =
|
final ResponseEntity<OAuth2AccessToken> accessToken =
|
||||||
this.tokenEndpoint.postAccessToken(usernamePasswordAuthenticationToken, params);
|
this.tokenEndpoint.postAccessToken(usernamePasswordAuthenticationToken, params);
|
||||||
final OAuth2AccessToken token = accessToken.getBody();
|
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) {
|
private String createOneTimeToken(final UserInfo account, final Long examId) {
|
||||||
|
|
||||||
// create a subject claim for this token only
|
|
||||||
final String subjectClaim = UUID.randomUUID().toString();
|
final String subjectClaim = UUID.randomUUID().toString();
|
||||||
this.storeSubjectForExam(examId, account.uuid, subjectClaim);
|
userDAO.changePassword(account.uuid, subjectClaim);
|
||||||
|
|
||||||
final Map<String, Object> claims = new HashMap<>();
|
final Map<String, Object> claims = new HashMap<>();
|
||||||
claims.put(USER_CLAIM, account.uuid);
|
claims.put(USER_CLAIM, account.uuid);
|
||||||
|
@ -276,38 +279,13 @@ public class TeacherAccountServiceImpl implements TeacherAccountService {
|
||||||
final Long examPK = Long.parseLong(examId);
|
final Long examPK = Long.parseLong(examId);
|
||||||
|
|
||||||
// check subject
|
// 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);
|
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"));
|
throw new APIMessage.APIMessageException(APIMessage.ErrorMessage.UNAUTHORIZED.of("Token subject mismatch"));
|
||||||
}
|
}
|
||||||
return claims;
|
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) {
|
private UserInfo synchronizeSPSUserForExam(final UserInfo account, final Long examId) {
|
||||||
if (this.screenProctoringService.isScreenProctoringEnabled(examId)) {
|
if (this.screenProctoringService.isScreenProctoringEnabled(examId)) {
|
||||||
this.screenProctoringService.synchronizeSPSUserForExam(examId);
|
this.screenProctoringService.synchronizeSPSUserForExam(examId);
|
||||||
|
|
|
@ -233,7 +233,7 @@ public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam>, BulkActionSup
|
||||||
|
|
||||||
@CacheEvict(
|
@CacheEvict(
|
||||||
cacheNames = ExamSessionCacheService.CACHE_NAME_RUNNING_EXAM,
|
cacheNames = ExamSessionCacheService.CACHE_NAME_RUNNING_EXAM,
|
||||||
key = "#examId")
|
key = "#exam.id")
|
||||||
Result<Exam> applySupporter(Exam exam, String userUUID);
|
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
|
/** 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);
|
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);
|
.getOr(exam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -379,7 +379,7 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
|
||||||
multiPartAttributes.add("token", this.accessToken);
|
multiPartAttributes.add("token", this.accessToken);
|
||||||
|
|
||||||
return super.postForObject(
|
return super.postForObject(
|
||||||
uploadEndpoint,
|
uri,
|
||||||
multiPartAttributes,
|
multiPartAttributes,
|
||||||
String.class);
|
String.class);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import java.util.List;
|
||||||
import java.util.concurrent.CompletionException;
|
import java.util.concurrent.CompletionException;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.NoResourceFoundException;
|
||||||
import org.apache.catalina.connector.ClientAbortException;
|
import org.apache.catalina.connector.ClientAbortException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -61,7 +62,7 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler {
|
||||||
((OnlyMessageLogExceptionWrapper) ex).log(log);
|
((OnlyMessageLogExceptionWrapper) ex).log(log);
|
||||||
return new ResponseEntity<>(status);
|
return new ResponseEntity<>(status);
|
||||||
} else {
|
} 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()));
|
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);
|
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)
|
@ExceptionHandler(ResourceNotFoundException.class)
|
||||||
public ResponseEntity<Object> handleResourceNotFoundException(
|
public ResponseEntity<Object> handleResourceNotFoundException(
|
||||||
final ResourceNotFoundException ex,
|
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(
|
this.authorization.checkRole(
|
||||||
institution,
|
institution,
|
||||||
EntityType.CLIENT_EVENT,
|
EntityType.CLIENT_EVENT,
|
||||||
UserRole.EXAM_ADMIN,
|
UserRole.EXAM_ADMIN, UserRole.EXAM_SUPPORTER, UserRole.TEACHER);
|
||||||
UserRole.EXAM_SUPPORTER);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result<Collection<ClientConnectionData>> getAllData(final FilterMap filterMap) {
|
private Result<Collection<ClientConnectionData>> getAllData(final FilterMap filterMap) {
|
||||||
|
|
|
@ -221,7 +221,9 @@ public class ClientEventController extends ReadonlyEntityController<ClientEvent,
|
||||||
.getUserService()
|
.getUserService()
|
||||||
.getCurrentUser()
|
.getCurrentUser()
|
||||||
.getUserRoles();
|
.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(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
|
|
|
@ -169,7 +169,7 @@ public class ExamMonitoringController {
|
||||||
this.authorization.checkRole(
|
this.authorization.checkRole(
|
||||||
institutionId,
|
institutionId,
|
||||||
EntityType.EXAM,
|
EntityType.EXAM,
|
||||||
UserRole.EXAM_SUPPORTER,
|
UserRole.EXAM_SUPPORTER, UserRole.TEACHER,
|
||||||
UserRole.EXAM_ADMIN);
|
UserRole.EXAM_ADMIN);
|
||||||
|
|
||||||
final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString());
|
final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString());
|
||||||
|
@ -230,7 +230,7 @@ public class ExamMonitoringController {
|
||||||
this.authorization.checkRole(
|
this.authorization.checkRole(
|
||||||
institutionId,
|
institutionId,
|
||||||
EntityType.EXAM,
|
EntityType.EXAM,
|
||||||
UserRole.EXAM_SUPPORTER,
|
UserRole.EXAM_SUPPORTER, UserRole.TEACHER,
|
||||||
UserRole.EXAM_ADMIN);
|
UserRole.EXAM_ADMIN);
|
||||||
|
|
||||||
final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString());
|
final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString());
|
||||||
|
@ -511,7 +511,7 @@ public class ExamMonitoringController {
|
||||||
this.authorization.checkRole(
|
this.authorization.checkRole(
|
||||||
institutionId,
|
institutionId,
|
||||||
EntityType.EXAM,
|
EntityType.EXAM,
|
||||||
UserRole.EXAM_SUPPORTER,
|
UserRole.EXAM_SUPPORTER, UserRole.TEACHER,
|
||||||
UserRole.EXAM_ADMIN);
|
UserRole.EXAM_ADMIN);
|
||||||
|
|
||||||
// check exam running
|
// check exam running
|
||||||
|
|
|
@ -90,8 +90,8 @@ public class LmsIntegrationController {
|
||||||
|
|
||||||
final EntityKey examID = fullLmsIntegrationService.deleteExam(lmsUUId, courseId, quizId)
|
final EntityKey examID = fullLmsIntegrationService.deleteExam(lmsUUId, courseId, quizId)
|
||||||
.onError(e -> log.error(
|
.onError(e -> log.error(
|
||||||
"Failed to delete exam: lmsId:{}, courseId: {}, quizId: {}",
|
"Failed to delete exam: lmsId:{}, courseId: {}, quizId: {}, error: {}",
|
||||||
lmsUUId, courseId, quizId, e))
|
lmsUUId, courseId, quizId, e.getMessage()))
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
log.info("Auto delete of exam successful: {}", examID);
|
log.info("Auto delete of exam successful: {}", examID);
|
||||||
|
@ -141,8 +141,7 @@ public class LmsIntegrationController {
|
||||||
@RequestMapping(
|
@RequestMapping(
|
||||||
path = API.LMS_FULL_INTEGRATION_LOGIN_TOKEN_ENDPOINT,
|
path = API.LMS_FULL_INTEGRATION_LOGIN_TOKEN_ENDPOINT,
|
||||||
method = RequestMethod.POST,
|
method = RequestMethod.POST,
|
||||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||||
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
|
|
||||||
public FullLmsIntegrationService.TokenLoginResponse getOneTimeLoginToken(
|
public FullLmsIntegrationService.TokenLoginResponse getOneTimeLoginToken(
|
||||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_LMS_UUID) final String lmsUUId,
|
@RequestParam(name = API.LMS_FULL_INTEGRATION_LMS_UUID) final String lmsUUId,
|
||||||
@RequestParam(name = API.LMS_FULL_INTEGRATION_COURSE_ID) final String courseId,
|
@RequestParam(name = API.LMS_FULL_INTEGRATION_COURSE_ID) final String courseId,
|
||||||
|
@ -166,7 +165,7 @@ public class LmsIntegrationController {
|
||||||
|
|
||||||
final String token = this.fullLmsIntegrationService
|
final String token = this.fullLmsIntegrationService
|
||||||
.getOneTimeLoginToken(lmsUUId, courseId, quizId, adHocAccountData)
|
.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();
|
.getOrThrow();
|
||||||
|
|
||||||
return new FullLmsIntegrationService.TokenLoginResponse(
|
return new FullLmsIntegrationService.TokenLoginResponse(
|
||||||
|
|
|
@ -68,7 +68,7 @@ public class RegisterUserController {
|
||||||
this.userActivityLogDAO = userActivityLogDAO;
|
this.userActivityLogDAO = userActivityLogDAO;
|
||||||
this.userDAO = userDAO;
|
this.userDAO = userDAO;
|
||||||
this.beanValidationService = beanValidationService;
|
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.autoActivation = BooleanUtils.isTrue(features.get(UserFeatures.Feature.ADMIN_USER_ACCOUNT_SELF_REGISTERING_AUTO_ACTIVATION.featureName));
|
||||||
this.requestRateLimitBucket = rateLimitService.createRequestLimitBucker();
|
this.requestRateLimitBucket = rateLimitService.createRequestLimitBucker();
|
||||||
this.createRateLimitBucket = rateLimitService.createCreationLimitBucker();
|
this.createRateLimitBucket = rateLimitService.createCreationLimitBucker();
|
||||||
|
|
|
@ -17,12 +17,14 @@ import java.util.Collection;
|
||||||
import javax.servlet.ServletOutputStream;
|
import javax.servlet.ServletOutputStream;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConnectionConfigurationChangeEvent;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.joda.time.DateTimeZone;
|
import org.joda.time.DateTimeZone;
|
||||||
import org.mybatis.dynamic.sql.SqlTable;
|
import org.mybatis.dynamic.sql.SqlTable;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.scheduling.annotation.EnableAsync;
|
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> {
|
public class SEBClientConfigController extends ActivatableEntityController<SEBClientConfig, SEBClientConfig> {
|
||||||
|
|
||||||
private final ConnectionConfigurationService sebConnectionConfigurationService;
|
private final ConnectionConfigurationService sebConnectionConfigurationService;
|
||||||
|
private final ApplicationEventPublisher applicationEventPublisher;
|
||||||
|
|
||||||
public SEBClientConfigController(
|
public SEBClientConfigController(
|
||||||
final SEBClientConfigDAO sebClientConfigDAO,
|
final SEBClientConfigDAO sebClientConfigDAO,
|
||||||
|
@ -70,7 +73,8 @@ public class SEBClientConfigController extends ActivatableEntityController<SEBCl
|
||||||
final BulkActionService bulkActionService,
|
final BulkActionService bulkActionService,
|
||||||
final PaginationService paginationService,
|
final PaginationService paginationService,
|
||||||
final BeanValidationService beanValidationService,
|
final BeanValidationService beanValidationService,
|
||||||
final ConnectionConfigurationService sebConnectionConfigurationService) {
|
final ConnectionConfigurationService sebConnectionConfigurationService,
|
||||||
|
final ApplicationEventPublisher applicationEventPublisher) {
|
||||||
|
|
||||||
super(authorization,
|
super(authorization,
|
||||||
bulkActionService,
|
bulkActionService,
|
||||||
|
@ -80,6 +84,7 @@ public class SEBClientConfigController extends ActivatableEntityController<SEBCl
|
||||||
beanValidationService);
|
beanValidationService);
|
||||||
|
|
||||||
this.sebConnectionConfigurationService = sebConnectionConfigurationService;
|
this.sebConnectionConfigurationService = sebConnectionConfigurationService;
|
||||||
|
this.applicationEventPublisher = applicationEventPublisher;
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(
|
@RequestMapping(
|
||||||
|
@ -183,6 +188,10 @@ public class SEBClientConfigController extends ActivatableEntityController<SEBCl
|
||||||
if (entity.isActive()) {
|
if (entity.isActive()) {
|
||||||
// try to get access token for SEB client
|
// try to get access token for SEB client
|
||||||
this.sebConnectionConfigurationService.initialCheckAccess(entity);
|
this.sebConnectionConfigurationService.initialCheckAccess(entity);
|
||||||
|
// notify all
|
||||||
|
applicationEventPublisher.publishEvent(new ConnectionConfigurationChangeEvent(
|
||||||
|
entity.institutionId,
|
||||||
|
entity.id));
|
||||||
}
|
}
|
||||||
return super.notifySaved(entity);
|
return super.notifySaved(entity);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.weblayer.oauth;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.core.annotation.Order;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
|
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
|
||||||
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
|
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
|
||||||
|
@ -113,4 +116,6 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
|
||||||
.tokenServices(defaultTokenServices);
|
.tokenServices(defaultTokenServices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue