TL; DR : Как обеспечить представление поля ресурса на основе ролей (json do c) и ограничить редактирование определенных полей только владельцем ресурса?
ДЕТАЛИ
Скажем, у меня есть документ из хранилища документов (couchbase) для различных «пользователей» как
@Document
@Data
public class UserProfile{
@Id
private String id;// this is same the ID of the user in Identity provider (keycloak). also =
//Principle.getName
private String name;
private String phone;
private String email;
private String somePersonalId;
private String myDescription;
}
и некоторые роли authz «ROLE_ADMIN», «ROLE_VERIFIED_VISITOR», «ROLE_SIGNED_UP_USER» где
ROLE_ADMIN может просматривать все поля любого «UserProfile»,
ROLE_VERIFIED_VISITOR может просматривать имя, телефон, адрес электронной почты и myDescription
ROLE_SIGNED_UP_USER может просматривать имя и myDescription
«Пользователь», владеющий данным «UserProfile», может просматривать все свои поля, кроме «id». Но ему разрешено редактировать только поля телефона и myDescription.
Обратите внимание, что в этом Json Do c может быть много таких полей и вложенных документов, и следование подходу «DTO» может быть слишком многословным. Для примера я привел простой документ.
Q1. Как обеспечить доступ к полям на основе ролей для различных ролей?
Я пробовал определять JsonViews, как показано ниже, и следуя этому подходу.
public class ProfileViews {
public static class SignedUpView{}
public static class VerifiedView extends SignedUpView{}
public static class SelfView extends VerifiedView{}
public static class AdminView extends SelfView{}
}
но что лучше всего делать "реактивным" (webflux) способом? [если вообще имеет смысл сделать его реактивным]
Q2. Как мне запретить пользователю редактировать определенные поля (адрес электронной почты, имя, somePersonalId в данном случае), но разрешить ему их просматривать?
У меня есть API исправлений для редактирования полей, как указано в коде ниже. Этот api принимает часть объекта UserProfile в качестве входных данных json и объединяет поля, переданные во входных данных json, с существующим UserProfile, имеющим «id» = {id}.
@PatchMapping(value="/profiles/{id}", produces = MediaType.APPLICATION_STREAM_JSON_VALUE)
public Mono<ResponseEntity<Profile>> patch(@PathVariable("id") String id,
@RequestBody UserProfile patchRequest,Principal principal){
repo
.findById(id)
.filter(profile -> !Objects.isNull(profile))
.filter(profile -> profile.getId().equals(principal.getName()))
.filter(profile->id.equals(principal.getName()))
.flatMap(profile->{
profile=APIUtils.mergePatch(profile,patchRequest,UserProfile.class);
return repo.save(profile);
})
.doOnSuccess(profile->{
log.info("Profile is patched for profile with id {}", id);
publisher.publishEvent(new ProfileUpdatedEvent(this,profile));// IGNORE THIS.
})
.flatMap(profileMono ->Mono.just( ResponseEntity.status(HttpStatus.OK).location(URI.create("/profiles/"+id)).body(profileMono)))
.defaultIfEmpty(ResponseEntity.status(HttpStatus.NOT_FOUND).body(new Profile()));
}
// APIUtils.mergePatch Method. "JsonMergePatch" package : //com.github.fge.jsonpatch.mergepatch.JsonMergePatch;
@SneakyThrows // from lombok
public static <T> T mergePatch(T source, T patch, Class<T> clazz){
JsonNode node = objectMapper.convertValue(source, JsonNode.class);
JsonNode patchNode = objectMapper.readTree(pojoToString(patch));
JsonMergePatch mergePatch = JsonMergePatch.fromJson(patchNode);
node = mergePatch.apply(node);
return objectMapper.treeToValue(node, clazz);
}
@SneakyThrows
public <T> String pojoToString(T t){
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(t);
}
Пожалуйста, дайте мне знать, если потребуется дополнительная информация / правки. Заранее спасибо!