UserUpdateService.java
/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2015 ForgeRock AS.
*/
package org.forgerock.selfservice.core;
import static org.forgerock.json.JsonValue.array;
import static org.forgerock.json.JsonValue.field;
import static org.forgerock.json.JsonValue.json;
import static org.forgerock.json.JsonValue.object;
import static org.forgerock.json.resource.Requests.newPatchRequest;
import javax.inject.Inject;
import org.forgerock.json.JsonPointer;
import org.forgerock.json.JsonValue;
import org.forgerock.json.resource.ActionRequest;
import org.forgerock.json.resource.ActionResponse;
import org.forgerock.json.resource.BadRequestException;
import org.forgerock.json.resource.CollectionResourceProvider;
import org.forgerock.json.resource.ConnectionFactory;
import org.forgerock.json.resource.CreateRequest;
import org.forgerock.json.resource.DeleteRequest;
import org.forgerock.json.resource.NotSupportedException;
import org.forgerock.json.resource.PatchOperation;
import org.forgerock.json.resource.PatchRequest;
import org.forgerock.json.resource.QueryRequest;
import org.forgerock.json.resource.QueryResourceHandler;
import org.forgerock.json.resource.QueryResponse;
import org.forgerock.json.resource.ReadRequest;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.json.resource.ResourcePath;
import org.forgerock.json.resource.ResourceResponse;
import org.forgerock.json.resource.UpdateRequest;
import org.forgerock.selfservice.core.annotations.SelfService;
import org.forgerock.selfservice.core.crypto.CryptoService;
import org.forgerock.selfservice.core.util.Answers;
import org.forgerock.services.context.Context;
import org.forgerock.util.promise.Promise;
/**
* A RequestHandler that proxies user requests to update the user's KBA answers.
*
* @since 0.8.0
*/
public final class UserUpdateService implements CollectionResourceProvider {
private static final String FIELD_QUESTION_ID = "questionId";
private static final String FIELD_CUSTOM_QUESTION = "customQuestion";
private static final String FIELD_ANSWER = "answer";
private final CryptoService cryptoService;
private final ConnectionFactory connectionFactory;
private final ResourcePath identityService;
private final JsonPointer kbaPropertyField;
/**
* Construct a service to update the user's KBA info.
*
* @param connectionFactory a ConnectionFactory with access to the <em>identityService</em> route.
* @param identityService the route to the identity service used to patch the user
* @param kbaPropertyField the pointer where KBA is stored in the user
*/
@Inject
public UserUpdateService(@SelfService ConnectionFactory connectionFactory, ResourcePath identityService,
JsonPointer kbaPropertyField) {
this.connectionFactory = connectionFactory;
this.cryptoService = new CryptoService();
this.identityService = identityService;
this.kbaPropertyField = kbaPropertyField;
}
@Override
public Promise<ActionResponse, ResourceException> actionCollection(Context context, ActionRequest request) {
return new NotSupportedException().asPromise();
}
@Override
public Promise<ActionResponse, ResourceException> actionInstance(Context context, String resourceId,
ActionRequest request) {
return new NotSupportedException().asPromise();
}
@Override
public Promise<ResourceResponse, ResourceException> createInstance(Context context, CreateRequest request) {
return new NotSupportedException().asPromise();
}
@Override
public Promise<ResourceResponse, ResourceException> deleteInstance(Context context, String resourceId,
DeleteRequest request) {
return new NotSupportedException().asPromise();
}
@Override
public Promise<ResourceResponse, ResourceException> patchInstance(Context context, String resourceId,
PatchRequest request) {
if (request.getPatchOperations().isEmpty() || request.getPatchOperations().size() > 1) {
return new BadRequestException("Patch expects one operation").asPromise();
}
PatchOperation patch = request.getPatchOperations().get(0);
if (!PatchOperation.OPERATION_REPLACE.equals(patch.getOperation())
|| !kbaPropertyField.equals(patch.getField())
|| !patch.getValue().isList()) {
return new BadRequestException("Patch operation must replace " + kbaPropertyField).asPromise();
}
try {
final JsonValue hashedAnswers = json(array());
for (JsonValue value : patch.getValue()) {
final JsonValue answer = value.get(FIELD_ANSWER);
if (answer.isNull()) {
throw new BadRequestException("Patch content must contain an " + FIELD_ANSWER);
}
final JsonValue hashedAnswer = Answers.hashAnswer(cryptoService, answer);
if (value.isDefined(FIELD_QUESTION_ID)) {
hashedAnswers.add(object(
field(FIELD_QUESTION_ID, value.get(FIELD_QUESTION_ID).asString()),
field(FIELD_ANSWER, hashedAnswer.getObject())));
} else if (value.isDefined(FIELD_CUSTOM_QUESTION)) {
hashedAnswers.add(object(
field(FIELD_CUSTOM_QUESTION, value.get(FIELD_CUSTOM_QUESTION).asString()),
field(FIELD_ANSWER, hashedAnswer.getObject())));
} else {
throw new BadRequestException("Patch content must contain either a " + FIELD_QUESTION_ID
+ " or a " + FIELD_CUSTOM_QUESTION);
}
}
return connectionFactory.getConnection().patch(
// do NOT wrap context in SelfServiceContext for this call -- it is authenticated as the user
// performing the patch/update
context,
newPatchRequest(identityService.child(resourceId),
PatchOperation.replace(kbaPropertyField, hashedAnswers.getObject())))
.asPromise();
} catch (ResourceException e) {
return e.asPromise();
}
}
@Override
public Promise<QueryResponse, ResourceException> queryCollection(Context context, QueryRequest request,
QueryResourceHandler handler) {
return new NotSupportedException().asPromise();
}
@Override
public Promise<ResourceResponse, ResourceException> readInstance(Context context, String resourceId,
ReadRequest request) {
return new NotSupportedException().asPromise();
}
@Override
public Promise<ResourceResponse, ResourceException> updateInstance(Context context, String resourceId,
UpdateRequest request) {
return new NotSupportedException().asPromise();
}
}