Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
*
* leads to (pseudocode): (createResource OR createAll) AND (updateResource OR updateAll)
*
* @return PermissionSecurityChecksBuilder
*/
PermissionSecurityChecksBuilder createPermissionPredicates() {
Map permissionCache = new HashMap<>();
for (var entry : targetToPermissionKeys.entrySet()) {
final AnnotationTarget securedTarget = entry.getKey();
final LogicalAndPermissionPredicate predicate = new LogicalAndPermissionPredicate();
// 'AND' operands
for (List permissionKeys : entry.getValue()) {
final boolean inclusive = isInclusive(permissionKeys);
// inclusive = false => permission1 OR permission2
// inclusive = true => permission1 AND permission2
if (inclusive) {
// 'AND' operands
for (PermissionKey permissionKey : permissionKeys) {
var permission = createPermission(permissionKey, securedTarget, permissionCache);
if (permission.isComputed()) {
predicate.markAsComputed();
}
// OR predicate with single operand is identity function
predicate.and(new LogicalOrPermissionPredicate().or(permission));
}
} else {
// 'OR' operands
var orPredicate = new LogicalOrPermissionPredicate();
predicate.and(orPredicate);
for (PermissionKey permissionKey : permissionKeys) {
var permission = createPermission(permissionKey, securedTarget, permissionCache);
if (permission.isComputed()) {
predicate.markAsComputed();
}
orPredicate.or(permission);
}
}
}
targetToPredicate.put(securedTarget, predicate);
}
return this;
}
private boolean isInclusive(List permissionKeys) {
// decide whether relation between permission specified via one annotation instance is 'AND' or 'OR'
// all PermissionKeys in the list 'permissionKeys' comes from same annotation, therefore we can
// safely pick flag from the first one
if (permissionKeys.isEmpty()) {
// permission keys should never ever be empty, this is just to stay on the safe side (avoid NPE)
return false;
}
return permissionKeys.get(0).inclusive;
}
PermissionSecurityChecksBuilder validatePermissionClasses(IndexView index) {
for (List> keyLists : targetToPermissionKeys.values()) {
for (List keyList : keyLists) {
for (PermissionKey key : keyList) {
if (!classSignatureToConstructor.containsKey(key.classSignature())) {
// validate permission class
final ClassInfo clazz = index.getClassByName(key.clazz.name());
Objects.requireNonNull(clazz);
if (clazz.constructors().size() != 1) {
throw new RuntimeException(
String.format("Permission class '%s' has %d constructors, exactly one is allowed",
key.classSignature(), clazz.constructors().size()));
}
var constructor = clazz.constructors().get(0);
// first constructor parameter must be permission name
if (constructor.parametersCount() == 0 || !STRING.equals(constructor.parameterType(0).name())) {
throw new RuntimeException(
String.format("Permission constructor '%s' first argument must be '%s'",
clazz.name().toString(), String.class.getName()));
}
// rest of validation needs to be done for computed classes only and per each secured method
// therefore we do it later
// cache validation result
classSignatureToConstructor.put(key.classSignature(), constructor);
}
}
}
}
return this;
}
PermissionSecurityChecksBuilder gatherPermissionsAllowedAnnotations(List instances,
Map alreadyCheckedMethods,
Map alreadyCheckedClasses,
List additionalClassInstances) {
// make sure we process annotations on methods first
instances.sort(new Comparator() {
@Override
public int compare(AnnotationInstance o1, AnnotationInstance o2) {
if (o1.target().kind() != o2.target().kind()) {
return o1.target().kind() == AnnotationTarget.Kind.METHOD ? -1 : 1;
}
// variable 'instances' won't be modified
return 0;
}
});
List cache = new ArrayList<>();
Map>> classMethodToPermissionKeys = new HashMap<>();
for (AnnotationInstance instance : instances) {
AnnotationTarget target = instance.target();
if (target.kind() == AnnotationTarget.Kind.METHOD) {
// method annotation
final MethodInfo methodInfo = target.asMethod();
// we don't allow combining @PermissionsAllowed with other security annotations as @DenyAll, ...
if (alreadyCheckedMethods.containsKey(methodInfo)) {
throw new IllegalStateException(
String.format("Method %s of class %s is annotated with multiple security annotations",
methodInfo.name(), methodInfo.declaringClass()));
}
gatherPermissionKeys(instance, methodInfo, cache, targetToPermissionKeys);
} else {
// class annotation
// add permissions for the class annotation if respective method haven't already been annotated
if (target.kind() == AnnotationTarget.Kind.CLASS) {
final ClassInfo clazz = target.asClass();
// ignore PermissionsAllowedInterceptor in security module
// we also need to check string as long as duplicate "PermissionsAllowedInterceptor" exists
// in RESTEasy Reactive, however this workaround should be removed when the interceptor is dropped
if (PERMISSIONS_ALLOWED_INTERCEPTOR.equals(clazz.name())
|| clazz.name().toString().endsWith("PermissionsAllowedInterceptor")) {
continue;
}
// check that class wasn't annotated with other security annotation
final AnnotationInstance existingClassInstance = alreadyCheckedClasses.get(clazz);
if (existingClassInstance == null) {
for (MethodInfo methodInfo : clazz.methods()) {
if (!isPublicNonStaticNonConstructor(methodInfo)) {
continue;
}
// ignore method annotated with other security annotation
boolean noMethodLevelSecurityAnnotation = !alreadyCheckedMethods.containsKey(methodInfo);
// ignore method annotated with method-level @PermissionsAllowed
boolean noMethodLevelPermissionsAllowed = !targetToPermissionKeys.containsKey(methodInfo);
if (noMethodLevelSecurityAnnotation && noMethodLevelPermissionsAllowed) {
gatherPermissionKeys(instance, methodInfo, cache, classMethodToPermissionKeys);
}
}
} else {
// we do not allow combining @PermissionsAllowed with other security annotations as @Authenticated
throw new IllegalStateException(
String.format("Class %s is annotated with multiple security annotations %s and %s", clazz,
instance.name(), existingClassInstance.name()));
}
}
}
}
targetToPermissionKeys.putAll(classMethodToPermissionKeys);
for (var instance : additionalClassInstances) {
gatherPermissionKeys(instance, instance.target(), cache, targetToPermissionKeys);
}
return this;
}
private static void gatherPermissionKeys(AnnotationInstance instance, T annotationTarget,
List cache,
Map>> targetToPermissionKeys) {
// @PermissionsAllowed value is in format permission:action, permission2:action, permission:action2, permission3
// here we transform it to permission -> actions
final var permissionToActions = new HashMap>();
for (String permissionToAction : instance.value().asStringArray()) {
if (permissionToAction.contains(PERMISSION_TO_ACTION_SEPARATOR)) {
// expected format: permission:action
final String[] permissionToActionArr = permissionToAction.split(PERMISSION_TO_ACTION_SEPARATOR);
if (permissionToActionArr.length != 2) {
throw new RuntimeException(String.format(
"PermissionsAllowed value '%s' contains more than one separator '%2$s', expected format is 'permissionName%2$saction'",
permissionToAction, PERMISSION_TO_ACTION_SEPARATOR));
}
final String permissionName = permissionToActionArr[0];
final String action = permissionToActionArr[1];
if (permissionToActions.containsKey(permissionName)) {
permissionToActions.get(permissionName).add(action);
} else {
final Set actions = new HashSet<>();
actions.add(action);
permissionToActions.put(permissionName, actions);
}
} else {
// expected format: permission
if (!permissionToActions.containsKey(permissionToAction)) {
permissionToActions.put(permissionToAction, new HashSet<>());
}
}
}
if (permissionToActions.isEmpty()) {
if (annotationTarget.kind() == AnnotationTarget.Kind.METHOD) {
throw new RuntimeException(String.format(
"Method '%s' was annotated with '@PermissionsAllowed', but no valid permission was provided",
annotationTarget.asMethod().name()));
} else {
throw new RuntimeException(String.format(
"Class '%s' was annotated with '@PermissionsAllowed', but no valid permission was provided",
annotationTarget.asClass().name()));
}
}
// permissions specified via @PermissionsAllowed has 'one of' relation, therefore we put them in one list
final List orPermissions = new ArrayList<>();
final String[] params = instance.value("params") == null ? new String[] { PermissionsAllowed.AUTODETECTED }
: instance.value("params").asStringArray();
final Type classType = instance.value("permission") == null ? Type.create(STRING_PERMISSION, Type.Kind.CLASS)
: instance.value("permission").asClass();
final boolean inclusive = instance.value("inclusive") != null && instance.value("inclusive").asBoolean();
for (var permissionToAction : permissionToActions.entrySet()) {
final var key = new PermissionKey(permissionToAction.getKey(), permissionToAction.getValue(), params,
classType, inclusive);
final int i = cache.indexOf(key);
if (i == -1) {
orPermissions.add(key);
cache.add(key);
} else {
orPermissions.add(cache.get(i));
}
}
// store annotation value as permission keys
targetToPermissionKeys
.computeIfAbsent(annotationTarget, at -> new ArrayList<>())
.add(List.copyOf(orPermissions));
}
private SecurityCheck createSecurityCheck(LogicalAndPermissionPredicate andPredicate) {
final SecurityCheck securityCheck;
final boolean isSinglePermissionGroup = andPredicate.operands.size() == 1;
if (isSinglePermissionGroup) {
final LogicalOrPermissionPredicate orPredicate = andPredicate.operands.iterator().next();
final boolean isSinglePermission = orPredicate.operands.size() == 1;
if (isSinglePermission) {
// single permission
final PermissionWrapper permissionWrapper = orPredicate.operands.iterator().next();
securityCheck = recorder.permissionsAllowed(permissionWrapper.computedPermission,
permissionWrapper.permission);
} else {
// multiple OR operands (permission OR permission OR ...)
if (andPredicate.atLeastOnePermissionIsComputed) {
securityCheck = recorder.permissionsAllowed(orPredicate.asComputedPermissions(recorder), null);
} else {
securityCheck = recorder.permissionsAllowed(null, orPredicate.asPermissions());
}
}
} else {
// permission group AND permission group AND permission group AND ...
// permission group = (permission OR permission OR permission OR ...)
if (andPredicate.atLeastOnePermissionIsComputed) {
final List>> computedPermissionGroups = new ArrayList<>();
for (LogicalOrPermissionPredicate permissionGroup : andPredicate.operands) {
computedPermissionGroups.add(permissionGroup.asComputedPermissions(recorder));
}
securityCheck = recorder.permissionsAllowedGroups(computedPermissionGroups, null);
} else {
final List>> permissionGroups = new ArrayList<>();
for (LogicalOrPermissionPredicate permissionGroup : andPredicate.operands) {
permissionGroups.add(permissionGroup.asPermissions());
}
securityCheck = recorder.permissionsAllowedGroups(null, permissionGroups);
}
}
return securityCheck;
}
private PermissionWrapper createPermission(PermissionKey permissionKey, AnnotationTarget securedTarget,
Map cache) {
var constructor = classSignatureToConstructor.get(permissionKey.classSignature());
return cache.computeIfAbsent(new PermissionCacheKey(permissionKey, securedTarget, constructor),
new Function() {
@Override
public PermissionWrapper apply(PermissionCacheKey permissionCacheKey) {
if (permissionCacheKey.computed) {
return new PermissionWrapper(createComputedPermission(permissionCacheKey), null);
} else {
final RuntimeValue permission;
if (permissionCacheKey.isStringPermission()) {
permission = createStringPermission(permissionCacheKey.permissionKey);
} else {
permission = createCustomPermission(permissionCacheKey);
}
return new PermissionWrapper(null, permission);
}
}
});
}
private Function