Skip to content

Commit

Permalink
feat: improve modifyProfile API and error handling
Browse files Browse the repository at this point in the history
- Support more attributes of UserType in modifyProfile API.
- Propagating policy error to client. The client can read the policy
  error through response metadata.
  • Loading branch information
wadahiro committed Jan 20, 2020
1 parent 7cffed9 commit 8b46c85
Show file tree
Hide file tree
Showing 11 changed files with 568 additions and 133 deletions.
11 changes: 11 additions & 0 deletions self-services/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@
<artifactId>protobuf-java</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,17 @@
import com.evolveum.midpoint.prism.delta.builder.S_ItemEntry;
import com.evolveum.midpoint.prism.delta.builder.S_MaybeDelete;
import com.evolveum.midpoint.prism.delta.builder.S_ValuesEntry;
import com.evolveum.midpoint.prism.path.ItemName;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.prism.schema.SchemaRegistry;
import com.evolveum.midpoint.schema.SchemaConstantsGenerated;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.LocalizableMessage;
import com.evolveum.midpoint.util.LocalizableMessageList;
import com.evolveum.midpoint.util.SingleLocalizableMessage;
import com.evolveum.midpoint.util.exception.*;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
Expand All @@ -33,8 +37,10 @@
import com.evolveum.midpoint.xml.ns._public.common.common_3.PasswordType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;
import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType;
import io.grpc.Metadata;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.grpc.protobuf.ProtoUtils;
import io.grpc.stub.StreamObserver;
import org.lognet.springboot.grpc.GRpcService;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -72,6 +78,18 @@ public class SelfServiceResource extends SelfServiceResourceGrpc.SelfServiceReso
@Autowired
protected transient AuthenticationEvaluator<PasswordAuthenticationContext> passwordAuthenticationEvaluator;

@Override
public Metadata handlePolicyViolationException(PolicyViolationException e) {
PolicyError error = TypeConverter.toPolicyError(e);
Metadata metadata = new Metadata();

Metadata.Key<PolicyError> POLICY_ERROR_KEY =
ProtoUtils.keyForProto(PolicyError.getDefaultInstance());
metadata.put(POLICY_ERROR_KEY, error);

return metadata;
}

@Override
public void modifyProfile(ModifyProfileRequest request, StreamObserver<ModifyProfileResponse> responseObserver) {
LOGGER.debug("Start modifyProfile");
Expand All @@ -89,41 +107,24 @@ public void modifyProfile(ModifyProfileRequest request, StreamObserver<ModifyPro

// https://wiki.evolveum.com/display/midPoint/Using+Prism+Deltas
for (UserItemDelta m : request.getModificationsList()) {
S_ValuesEntry v = null;
boolean isPolyString = false;
switch (m.getName()) {
case NAME:
v = i.item(UserType.F_NAME);
isPolyString = true;
break;
case EMAIL_ADDRESS:
v = i.item(UserType.F_EMAIL_ADDRESS);
break;
case GIVEN_NAME:
v = i.item(UserType.F_GIVEN_NAME);
isPolyString = true;
break;
case FAMILY_NAME:
v = i.item(UserType.F_FAMILY_NAME);
isPolyString = true;
break;
}
UserItemPath path = m.getName();

if (v == null) {
LOGGER.warn("Invalid argument. Unsupported name: {}", m.getName());
throw new StatusRuntimeException(Status.INVALID_ARGUMENT);
}
ItemName itemName = TypeConverter.toItemName(path);
S_ValuesEntry v = i.item(itemName);

S_ItemEntry entry = null;
if (!m.getValuesToAdd().isEmpty()) {
S_MaybeDelete av = v.add(asStringOrPolyString(m.getValuesToAdd(), isPolyString));
S_MaybeDelete av = v.add(TypeConverter.toValue(path, m.getValuesToAdd()));

if (!m.getValuesToDelete().isEmpty()) {
entry = av.delete(asStringOrPolyString(m.getValuesToDelete(), isPolyString));
entry = av.delete(TypeConverter.toValue(path, m.getValuesToDelete()));
}

} else if (!m.getValuesToReplace().isEmpty()) {
entry = v.replace(asStringOrPolyString(m.getValuesToReplace(), isPolyString));
entry = v.replace(TypeConverter.toValue(path, m.getValuesToReplace()));

} else if (!m.getValuesToDelete().isEmpty()) {
entry = v.delete(asStringOrPolyString(m.getValuesToDelete(), isPolyString));
entry = v.delete(TypeConverter.toValue(path, m.getValuesToDelete()));
}

if (entry == null) {
Expand All @@ -140,6 +141,10 @@ public void modifyProfile(ModifyProfileRequest request, StreamObserver<ModifyPro
modelCrudService.modifyObject(UserType.class, user.getOid(), deltas, options, task, updateResult);

updateResult.computeStatus();
} catch (UnsupportedOperationException e) {
LOGGER.warn("Invalid argument. ", e);
throw new StatusRuntimeException(Status.INVALID_ARGUMENT);

} catch (ObjectAlreadyExistsException | PolicyViolationException e) {
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't update user changes", e);
throw e;
Expand All @@ -162,13 +167,6 @@ public void modifyProfile(ModifyProfileRequest request, StreamObserver<ModifyPro
LOGGER.debug("End updateProfile");
}

private Object asStringOrPolyString(String s, boolean isPolyString) {
if (isPolyString) {
return PolyString.fromOrig(s);
}
return s;
}

@Override
public void updateCredential(UpdateCredentialRequest request, StreamObserver<UpdateCredentialResponse> responseObserver) {
LOGGER.debug("Start updateCredential");
Expand Down Expand Up @@ -205,7 +203,6 @@ public void forceUpdateCredential(ForceUpdateCredentialRequest request, StreamOb
public void requestRole(RequestRoleRequest request, StreamObserver<RequestRoleResponse> responseObserver) {
LOGGER.debug("Start requestRole");



LOGGER.debug("End requestRole");
}
Expand Down Expand Up @@ -256,7 +253,10 @@ protected void updateCredential(MidPointTaskContext ctx, String oldCred, String
modelService.executeChanges(deltas, null, task, updateResult);

updateResult.computeStatus();
} catch (EncryptionException | ObjectAlreadyExistsException | PolicyViolationException e) {
} catch (PolicyViolationException e) {
LoggingUtils.logExceptionAsWarning(LOGGER, "Couldn't save password changes because of policy violation: {}", e, e.getMessage());
throw e;
} catch (EncryptionException | ObjectAlreadyExistsException e) {
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't save password changes", e);
throw e;
} finally {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package jp.openstandia.midpoint.grpc;

import com.evolveum.midpoint.prism.path.ItemName;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.util.LocalizableMessage;
import com.evolveum.midpoint.util.LocalizableMessageList;
import com.evolveum.midpoint.util.SingleLocalizableMessage;
import com.evolveum.midpoint.util.exception.PolicyViolationException;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;

import javax.xml.bind.annotation.XmlElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;

public class TypeConverter {

private static Map<UserItemPath, ItemName> userTypeMap = new HashMap<>();
private static Map<ItemName, Class> userValueTypeMap = new HashMap<>();

static {
Map<String, ItemName> strToItemName = new HashMap<>();

Field[] fields = UserType.class.getFields();
Arrays.stream(fields)
.filter(x -> x.getName().startsWith("F_") && x.getType() == ItemName.class)
.forEach(x -> {
String name = x.getName();
try {
UserItemPath path = UserItemPath.valueOf(name);
ItemName itemName = (ItemName) x.get(null);
userTypeMap.put(path, itemName);
strToItemName.put(itemName.getLocalPart(), itemName);
} catch (IllegalArgumentException | IllegalAccessException ignore) {
}
});

Method[] methods = UserType.class.getMethods();
Arrays.stream(methods)
.filter(x -> x.isAnnotationPresent(XmlElement.class))
.forEach(x -> {
XmlElement ele = x.getAnnotation(XmlElement.class);

ItemName itemName = strToItemName.get(ele.name());
if (itemName == null) {
return;
}
Class<?> returnType = x.getReturnType();

if (returnType.isAssignableFrom(List.class)) {
Type genericReturnType = x.getGenericReturnType();
String typeName = genericReturnType.getTypeName();
if (typeName.contains(String.class.getName())) {
userValueTypeMap.put(itemName, String.class);
} else if (typeName.contains(PolyStringType.class.getName())) {
userValueTypeMap.put(itemName, PolyStringType.class);
} else {
throw new UnsupportedOperationException(itemName + " is not supported");
}
} else {
userValueTypeMap.put(itemName, returnType);
}
});
}

public static ItemName toItemName(UserItemPath path) {
ItemName itemName = userTypeMap.get(path);
if (itemName == null) {
throw new UnsupportedOperationException(path + " is not supported");
}
return itemName;
}

public static Object toValue(UserItemPath path, String value) {
ItemName itemName = toItemName(path);

Class clazz = userValueTypeMap.get(itemName);

if (clazz.isAssignableFrom(String.class)) {
return value;
}
if (clazz.isAssignableFrom(PolyStringType.class)) {
return PolyString.fromOrig((String) value);
}

throw new UnsupportedOperationException(path + " is not supported");
}

public static PolicyError toPolicyError(PolicyViolationException e) {
PolicyError.Builder builder = PolicyError.newBuilder();
MessageWrapper wrapper = null;

LocalizableMessage msg = e.getUserFriendlyMessage();
if (msg instanceof SingleLocalizableMessage) {
wrapper = toMessageWrapper((SingleLocalizableMessage) msg);

} else if (msg instanceof LocalizableMessageList) {
wrapper = toMessageWrapper((LocalizableMessageList) msg);
}

if (wrapper == null) {
throw new UnsupportedOperationException(msg.getClass() + " is not supported");
}

builder.addErrors(wrapper);

return builder.build();
}

private static MessageWrapper toMessageWrapper(LocalizableMessageList list) {
MessageList messageList = toMessageList(list);
return MessageWrapper.newBuilder()
.setMsgListArg(messageList)
.build();
}

private static MessageList toMessageList(LocalizableMessageList list) {
MessageList.Builder builder = MessageList.newBuilder();

for (LocalizableMessage msg : list.getMessages()) {
MessageWrapper wrapper = null;
if (msg instanceof SingleLocalizableMessage) {
wrapper = toMessageWrapper((SingleLocalizableMessage) msg);

} else if (msg instanceof LocalizableMessageList) {
wrapper = toMessageWrapper((LocalizableMessageList) msg);
}

builder.addArgs(wrapper);
}

return builder.build();
}

private static MessageWrapper toMessageWrapper(SingleLocalizableMessage msg) {
return MessageWrapper.newBuilder()
.setMsgArg(toMessage(msg))
.build();
}

private static Message toMessage(SingleLocalizableMessage msg) {
return Message.newBuilder()
.setKey(msg.getKey())
.addAllArgs(toMessageWrappers(msg.getArgs()))
.build();
}

private static Iterable<? extends MessageWrapper> toMessageWrappers(Object[] args) {
List<MessageWrapper> list = new ArrayList<>();

for (Object arg : args) {
MessageWrapper wrapper;

if (arg instanceof SingleLocalizableMessage) {
wrapper = toMessageWrapper((SingleLocalizableMessage) arg);
} else if (arg instanceof LocalizableMessageList) {
wrapper = toMessageWrapper((LocalizableMessageList) arg);
} else {
wrapper = MessageWrapper.newBuilder()
.setStringArg(arg != null ? arg.toString() : "")
.build();
}

list.add(wrapper);
}

return list;
}
}
Loading

0 comments on commit 8b46c85

Please sign in to comment.