SEBSERV-335 feature complete

This commit is contained in:
anhefti 2022-11-28 16:37:26 +01:00
parent 9d80a94bbf
commit d2d3d3f864
13 changed files with 125 additions and 86 deletions

View file

@ -31,8 +31,6 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM;
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType;
import ch.ethz.seb.sebserver.gbl.util.Utils;
@JsonIgnoreProperties(ignoreUnknown = true)

View file

@ -425,6 +425,11 @@ public enum ActionDefinition {
ImageIcon.NO_SHIELD,
PageStateDefinitionImpl.SECURITY_KEY_EDIT,
ActionCategory.FORM),
EXAM_RELOAD_SECURITY_KEY_VIEW(
new LocTextKey("sebserver.exam.signaturekey.action.edit"),
ImageIcon.SHIELD,
PageStateDefinitionImpl.SECURITY_KEY_EDIT,
ActionCategory.FORM),
EXAM_SECURITY_KEY_SAVE_SETTINGS(
new LocTextKey("sebserver.exam.signaturekey.action.save"),

View file

@ -27,16 +27,18 @@ import ch.ethz.seb.sebserver.gbl.model.institution.AppSignatureKeyInfo;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
import ch.ethz.seb.sebserver.gui.form.FormHandle;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetClientConnections;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GrantClientConnectionSecurityKey;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.seckey.GrantAppSignatureKey;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@ -55,6 +57,11 @@ public class AddSecurityKeyGrantPopup {
private static final LocTextKey TITLE_TEXT_FORM_TAG =
new LocTextKey("sebserver.exam.signaturekey.seb.add.tag");
private static final LocTextKey TABLE_TITLE =
new LocTextKey("sebserver.exam.signaturekey.list.title");
private static final LocTextKey TABLE_TITLE_TOOLTIP =
new LocTextKey("sebserver.exam.signaturekey.list.title" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
private static final LocTextKey TABLE_COLUMN_NAME =
new LocTextKey("sebserver.exam.signaturekey.list.name");
private static final LocTextKey TABLE_COLUMN_INFO =
@ -77,6 +84,7 @@ public class AddSecurityKeyGrantPopup {
action.pageContext().getParent().getShell(),
this.pageService.getWidgetFactory());
dialog.setDialogWidth(800);
//dialog.setDialogHeight(600);
final Predicate<FormHandle<?>> applyGrant = formHandle -> applyGrant(
pageContext,
@ -144,7 +152,17 @@ public class AddSecurityKeyGrantPopup {
.withQueryParam(API.PARAM_MODEL_ID_LIST, clientConnectionIds)
.call()
.onSuccess(connections -> {
final List<ClientConnection> list = new ArrayList<>();
widgetFactory.addFormSubContextHeader(
formContext.getParent(),
TABLE_TITLE,
TABLE_TITLE_TOOLTIP);
final List<ClientConnection> list = new ArrayList<>(this.pageService
.getRestService()
.getBuilder(GetClientConnections.class)
.withQueryParam(API.PARAM_MODEL_ID_LIST, clientConnectionIds)
.call().getOrThrow());
this.pageService.staticListTableBuilder(list, EntityType.CLIENT_CONNECTION)
.withPaging(10)
@ -165,7 +183,8 @@ public class AddSecurityKeyGrantPopup {
TABLE_COLUMN_STATUS,
row -> this.pageService.getResourceService()
.localizedClientConnectionStatusName(row.getStatus()))
.widthProportion(1));
.widthProportion(1))
.compose(formContext);
});
@ -184,15 +203,35 @@ public class AddSecurityKeyGrantPopup {
final Long connectioId = appSignatureKeyInfo.connectionIds.keySet().iterator().next();
return this.pageService
final boolean hasValue = this.pageService
.getRestService()
.getBuilder(GrantClientConnectionSecurityKey.class)
.getBuilder(GrantAppSignatureKey.class)
.withURIVariable(API.PARAM_PARENT_MODEL_ID, String.valueOf(appSignatureKeyInfo.examId))
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(connectioId))
.withFormBinding(formHandle.getFormBinding())
.call()
.onError(formHandle::handleError)
.onError(error -> {
if (error.getMessage().contains("\"messageCode\":\"1010\"")) {
pageContext.publishInfo(new LocTextKey("sebserver.monitoring.signaturegrant.message.granted"));
} else {
formHandle.handleError(error);
}
})
.hasValue();
if (hasValue) {
final PageContext reloadContext = pageContext.withEntityKey(pageContext.getParentEntityKey());
final PageAction action = this.pageService.pageActionBuilder(reloadContext)
.newAction(ActionDefinition.EXAM_RELOAD_SECURITY_KEY_VIEW)
.create();
this.pageService.firePageEvent(
new ActionEvent(action),
action.pageContext());
}
return hasValue;
}
}

View file

@ -68,8 +68,6 @@ public class ExamSignatureKeyForm implements TemplateComposer {
new LocTextKey("sebserver.exam.signaturekey.keylist.key");
private static final LocTextKey APP_SIG_KEY_LIST_NUM_CLIENTS =
new LocTextKey("sebserver.exam.signaturekey.keylist.clients");
private static final LocTextKey APP_SIG_KEY_LIST_CLIENT_IDS =
new LocTextKey("sebserver.exam.signaturekey.keylist.clientids");
private static final LocTextKey APP_SIG_KEY_LIST_EMPTY_SELECTION_TEXT_KEY =
new LocTextKey("sebserver.exam.signaturekey.keylist.pleaseSelect");
@ -78,7 +76,7 @@ public class ExamSignatureKeyForm implements TemplateComposer {
private static final LocTextKey GRANT_LIST_TITLE_TOOLTIP =
new LocTextKey("sebserver.exam.signaturekey.grantlist.title" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
private static final LocTextKey GRANT_LIST_EMPTY_LIST_TEXT_KEY =
new LocTextKey("sebserver.exam.signaturekey.grantlist..empty");
new LocTextKey("sebserver.exam.signaturekey.grantlist.empty");
private static final LocTextKey GRANT_LIST_KEY =
new LocTextKey("sebserver.exam.signaturekey.grantlist.key");
private static final LocTextKey GRANT_LIST_TAG =

View file

@ -29,7 +29,7 @@ import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GrantClientConnectionSecurityKey;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.seckey.GrantAppSignatureKey;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@Lazy
@ -131,7 +131,7 @@ public class SignatureKeyGrantPopup {
return this.pageService
.getRestService()
.getBuilder(GrantClientConnectionSecurityKey.class)
.getBuilder(GrantAppSignatureKey.class)
.withURIVariable(API.PARAM_PARENT_MODEL_ID, examKey.modelId)
.withURIVariable(API.PARAM_MODEL_ID, connectionKey.modelId)
.withFormBinding(formHandle.getFormBinding())

View file

@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session;
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.seckey;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
@ -24,9 +24,9 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GrantClientConnectionSecurityKey extends RestCall<SecurityKey> {
public class GrantAppSignatureKey extends RestCall<SecurityKey> {
public GrantClientConnectionSecurityKey() {
public GrantAppSignatureKey() {
super(new TypeKey<>(
CallType.GET_SINGLE,
EntityType.SEB_SECURITY_KEY_REGISTRY,
@ -34,9 +34,9 @@ public class GrantClientConnectionSecurityKey extends RestCall<SecurityKey> {
}),
HttpMethod.POST,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_MONITORING_ENDPOINT +
API.EXAM_ADMINISTRATION_ENDPOINT +
API.PARENT_MODEL_ID_VAR_PATH_SEGMENT +
API.EXAM_MONITORING_SIGNATURE_KEY_ENDPOINT +
API.EXAM_ADMINISTRATION_SEB_SECURITY_KEY_GRANTS_PATH_SEGMENT +
API.MODEL_ID_VAR_PATH_SEGMENT);
}

View file

@ -33,7 +33,7 @@ public class SaveAppSignatureKeySettings extends RestCall<Exam> {
new TypeReference<Exam>() {
}),
HttpMethod.POST,
MediaType.APPLICATION_JSON,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_ADMINISTRATION_ENDPOINT
+ API.PARENT_MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_SEB_SECURITY_KEY_INFO_PATH_SEGMENT);

View file

@ -111,6 +111,8 @@ public class ExamAdminServiceImpl implements ExamAdminService {
.error("Failed to store ADDITIONAL_ATTR_STATISTICAL_GRANT_COUNT_THRESHOLD: ", error));
}
this.examDAO.setModified(examId);
}).flatMap(v -> this.examDAO.byPK(examId));
}

View file

@ -42,12 +42,6 @@ public interface SecurityKeyService {
* @return Result refer to the list of security key registry entries or to an error when happened */
Result<Collection<SecurityKey>> getSecurityKeyEntries(Long institutionId, Long examId, KeyType type);
/** Register a new security key entry in the registry.
*
* @param key The security key data
* @return Result refer to the newly created and stored security key entry or to an error when happened */
Result<SecurityKey> registerSecurityKey(SecurityKey key);
/** Register SEB client connection App-Signature-Key as a new global security key registry entry
* This is equivalent to make a global grant for specified App-Signature-Key of given SEB client connection.
*
@ -57,15 +51,14 @@ public interface SecurityKeyService {
* @return Result refer to the newly created security key entry or to an error when happened */
Result<SecurityKey> registerGlobalAppSignatureKey(Long institutionId, Long connectionId, String tag);
/** Register SEB client connection App-Signature-Key as a new exam based security key registry entry
* This is equivalent to make a exam specific grant for specified App-Signature-Key of given SEB client connection.
/** Grants an App-Signature-Key sent by a SEB client and register it within the granted key registry
*
* @param institutionId The institution identifier
* @param examId The exam identifier for the exam based grant
* @param connectionId The client connection identifier
* @param tag A Tag for user identification of the grant within the registry
* @return Result refer to the newly created security key entry or to an error when happened */
Result<SecurityKey> registerExamAppSignatureKey(Long institutionId, Long examId, Long connectionId, String tag);
Result<SecurityKey> grantAppSignatureKey(Long institutionId, Long examId, Long connectionId, String tag);
/** Used to apply a SEB client App-signature-Key check for a given App-Signature-Key sent by the SEB.
* Note: This also stores the given App-Signature-Key sent by SEB if not already stored for the SEB connection.

View file

@ -26,7 +26,9 @@ import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.institution.AppSignatureKeyInfo;
@ -121,6 +123,12 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
final Long connectionId,
final String tag) {
if (StringUtils.isEmpty(tag)) {
throw new FieldValidationException(
Domain.SEB_SECURITY_KEY_REGISTRY.ATTR_TAG,
"securityKeyGrant:tag:mandatory");
}
if (log.isDebugEnabled()) {
log.debug("Register app-signature-key global grant. ConnectionId: {} tag: {}",
connectionId,
@ -138,12 +146,18 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
}
@Override
public Result<SecurityKey> registerExamAppSignatureKey(
public Result<SecurityKey> grantAppSignatureKey(
final Long institutionId,
final Long examId,
final Long connectionId,
final String tag) {
if (StringUtils.isEmpty(tag)) {
throw new FieldValidationException(
Domain.SEB_SECURITY_KEY_REGISTRY.ATTR_TAG,
"securityKeyGrant:tag:notNull");
}
if (log.isDebugEnabled()) {
log.debug("Register app-signature-key exam grant. Exam: {} connectionId: {} tag: {}",
examId,
@ -239,11 +253,11 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
}
}
@Override
public Result<SecurityKey> registerSecurityKey(final SecurityKey key) {
return this.encryptInternal(key)
.flatMap(this.securityKeyRegistryDAO::createNew);
}
// @Override
// public Result<SecurityKey> registerSecurityKey(final SecurityKey key) {
// return this.encryptInternal(key)
// .flatMap(this.securityKeyRegistryDAO::createNew);
// }
@Override
public Result<EntityKey> deleteSecurityKeyGrant(final Long keyId) {
@ -366,7 +380,9 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
final Long examId,
final String decryptedSignature) {
System.out.println("****************** statisticalCheck: " + decryptedSignature);
if (log.isDebugEnabled()) {
log.debug("Apply statistical security check update for exam {}", examId);
}
// if there is no exam known yet, no statistical check can be applied
if (examId == null) {
@ -378,8 +394,7 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
// TODO if cert encryption is available check if exam has defined cert for decryption
final Certificate cert = null;
final int matches = this.clientConnectionDAO
.getAllActiveConnectionTokens(examId)
final int matches = this.clientConnectionDAO.getConnectionTokens(examId)
.map(tokens -> tokens.stream()
.map(this.examSessionCacheService::getClientConnection)
.filter(cc -> matchOtherClientConnection(cc.clientConnection, decryptedSignature, cert))
@ -410,12 +425,20 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
return false; // NOTE: not supported yet
}
if (cc.status != ConnectionStatus.ACTIVE && cc.status != ConnectionStatus.CLOSED) {
return false;
}
if (log.isDebugEnabled()) {
log.debug("Apply statistical security check update for client connection {}", cc);
}
return Objects.equals(
decryptedSignature,
decryptStoredSignatureForConnection(cc));
} catch (final Exception e) {
log.warn("Failed to get and decrypt app signature key for client connection: {}", cc, e);
log.warn("Failed to apply statistical security check update for client connection: {}", cc, e);
return false;
}
}
@ -501,16 +524,16 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
.getOr(1);
}
private Result<SecurityKey> encryptInternal(final SecurityKey key) {
return Result.tryCatch(() -> new SecurityKey(
key.id,
key.institutionId,
key.keyType,
Utils.toString(this.cryptor.encrypt(key.key).getOrThrow()),
key.tag,
key.examId,
key.examTemplateId));
}
// private Result<SecurityKey> encryptInternal(final SecurityKey key) {
// return Result.tryCatch(() -> new SecurityKey(
// key.id,
// key.institutionId,
// key.keyType,
// Utils.toString(this.cryptor.encrypt(key.key).getOrThrow()),
// key.tag,
// key.examId,
// key.examTemplateId));
// }
private Collection<SecurityKey> getKeysForRead(final Collection<SecurityKey> keys) {
return keys.stream()

View file

@ -17,14 +17,12 @@ import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.mybatis.dynamic.sql.SqlTable;
import org.springframework.http.MediaType;
import org.springframework.util.MultiValueMap;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
@ -216,7 +214,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_SEB_SECURITY_KEY_INFO_PATH_SEGMENT,
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public void saveAppSignatureKeySettings(
@PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId,
@RequestParam(
@ -258,28 +256,32 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
}
@RequestMapping(
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_SEB_SECURITY_KEY_GRANTS_PATH_SEGMENT,
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT +
API.EXAM_ADMINISTRATION_SEB_SECURITY_KEY_GRANTS_PATH_SEGMENT +
API.MODEL_ID_VAR_PATH_SEGMENT,
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public SecurityKey newSecurityGrant(
@PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId,
public SecurityKey grantAppSignatureKey(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@RequestParam final MultiValueMap<String, String> allRequestParams,
final HttpServletRequest request) {
@PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId,
@PathVariable(name = API.PARAM_MODEL_ID, required = true) final Long connectionId,
@RequestParam(name = Domain.SEB_SECURITY_KEY_REGISTRY.ATTR_TAG, required = false) final String tagName) {
this.checkWritePrivilege(institutionId);
return this.examDAO.byPK(examId)
.flatMap(this::checkReadAccess)
.flatMap(exam -> {
final POSTMapper postMap = new POSTMapper(allRequestParams, request.getQueryString())
.putIfAbsent(API.PARAM_INSTITUTION_ID, String.valueOf(institutionId));
return this.securityKeyService.registerSecurityKey(new SecurityKey(postMap));
})
.flatMap(exam -> this.securityKeyService.grantAppSignatureKey(
institutionId,
examId,
connectionId,
tagName))
.flatMap(this.userActivityLogDAO::logCreate)
.onSuccess(key -> this.securityKeyService.updateAppSignatureKeyGrants(examId))
.getOrThrow();
}

View file

@ -462,30 +462,6 @@ public class ExamMonitoringController {
}
}
@RequestMapping(
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT +
API.EXAM_MONITORING_SIGNATURE_KEY_ENDPOINT +
API.MODEL_ID_VAR_PATH_SEGMENT,
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public SecurityKey grantAppSignatureKey(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId,
@PathVariable(name = API.PARAM_MODEL_ID, required = true) final Long connectionId,
@RequestParam(name = Domain.SEB_SECURITY_KEY_REGISTRY.ATTR_TAG, required = true) final String tagName) {
checkPrivileges(institutionId, examId);
return this.securityKeyService
.registerExamAppSignatureKey(institutionId, examId, connectionId, tagName)
.onSuccess(key -> this.securityKeyService.updateAppSignatureKeyGrants(examId))
.getOrThrow();
}
@RequestMapping(
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT +
API.EXAM_MONITORING_SIGNATURE_KEY_ENDPOINT +

View file

@ -819,10 +819,13 @@ sebserver.exam.signaturekey.keylist.clientids.tooltip=List of SEB Client session
sebserver.exam.signaturekey.keylist.pleaseSelect=Please select an App Signature Key from the list.
sebserver.exam.signaturekey.seb.title=App Signature Key
sebserver.exam.signaturekey.seb.add.title=Grant App Signature Key
sebserver.exam.signaturekey.seb.add.info=Please set a meaningful Tag Name and use OK to confirm this security key as granted.
sebserver.exam.signaturekey.seb.add.signature=Key Hash
sebserver.exam.signaturekey.seb.add.tag=Tag Name
sebserver.exam.signaturekey.list.title=SEB client connections
sebserver.exam.signaturekey.list.title.tooltip=List of SEB client connections with this App Signature Key
sebserver.exam.signaturekey.list.name=SEB Session ID
sebserver.exam.signaturekey.list.info=SEB Client Info
sebserver.exam.signaturekey.list.status=Connection Status