diff --git a/src/main/java/io/quarkus/security/PermissionChecker.java b/src/main/java/io/quarkus/security/PermissionChecker.java new file mode 100644 index 0000000..6d443f8 --- /dev/null +++ b/src/main/java/io/quarkus/security/PermissionChecker.java @@ -0,0 +1,66 @@ +package io.quarkus.security; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Used to annotate CDI bean methods that check if a user holds a permission specified by the {@link #value()}. + * Quarkus Security will augment every {@link io.quarkus.security.identity.SecurityIdentity} with this permission checker. + * Such a permission checker grants access to methods secured with the {@link PermissionsAllowed} security annotation. + * Following example shows how it works: + *
+ * {@code + * @Path("hello") + * public class HelloResource { + * + * @PermissionsAllowed("speak") + * @GET + * public String sayHello() { + * return "Hello World!"; + * } + * + * @PermissionChecker("speak") + * public boolean canSpeak(SecurityIdentity identity) { + * return "speaker".equals(identity.getPrincipal().getName()); + * } + * } + * } + *+ * Parameters of permission checker methods can include any of secured method parameters. + * Consider following secured method: + *
+ * {@code + * @PermissionsAllowed("update") + * public String updateString(String a, String b, String c, String d) { + * ... + * } + * } + *+ * Permission checker that grants access to the {@code updateString} method can inject + * any arguments it requires and optionally even {@link io.quarkus.security.identity.SecurityIdentity}: + *
+ * {@code + * @PermissionChecker("update") + * public boolean canUpdate(String c, String a, SecurityIdentity identity) { + * ... + * } + * } + *+ * Permission checker method parameters are matched with the secured method parameters in exactly same fashion + * as are constructor parameters of a custom permission. Please see {@link PermissionsAllowed#params()} for more information. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface PermissionChecker { + + /** + * Specifies a permission this checker grants. + * + * @see PermissionsAllowed#value() + * @return name of the permission this checker grants + */ + String value(); + +} diff --git a/src/main/java/io/quarkus/security/PermissionsAllowed.java b/src/main/java/io/quarkus/security/PermissionsAllowed.java index 20ce78f..a733b1e 100644 --- a/src/main/java/io/quarkus/security/PermissionsAllowed.java +++ b/src/main/java/io/quarkus/security/PermissionsAllowed.java @@ -132,12 +132,15 @@ * * * To see how the example above is evaluated, please see "implies" method of your {@link #permission()}. + *
+ * This attribute can be empty when {@link #permission()} is set to the {@link QuarkusPermission} as that permission + * does not require any name. * * @see StringPermission#implies(Permission) for more details on how above-mentioned example is evaluated * * @return permissions linked to respective actions */ - String[] value(); + String[] value() default {}; /** * Choose a relation between permissions specified via {@link #value()}. By default, at least one of permissions @@ -280,6 +283,8 @@ * } * * + * + * {@code name} may only be omitted when {@link #permission()} is set to the {@link QuarkusPermission} * {@code actions} parameter is optional and may be omitted. * * @return permission class diff --git a/src/main/java/io/quarkus/security/QuarkusPermission.java b/src/main/java/io/quarkus/security/QuarkusPermission.java new file mode 100644 index 0000000..aeda167 --- /dev/null +++ b/src/main/java/io/quarkus/security/QuarkusPermission.java @@ -0,0 +1,72 @@ +package io.quarkus.security; + +import io.quarkus.security.identity.SecurityIdentity; + +import java.security.Permission; +import java.util.Objects; + +/** + * Special type of the {@link PermissionsAllowed#permission()} that does not require {@link SecurityIdentity} + * augmentation. When this permission is set to the {@link PermissionsAllowed#permission()}, Quarkus Security augments + * all the {@link SecurityIdentity} with a permission checker for you. + * This way, access to methods secured with the {@link PermissionsAllowed} annotation will only be granted + * when {@link #isGranted(SecurityIdentity)} returns true. + */ +public abstract class QuarkusPermission extends Permission { + + private static final String QUARKUS_PERMISSION = "io.quarkus.security.permission#name"; + + /** + * Permission name is set to the {@link #QUARKUS_PERMISSION}. + * Subclasses can declare constructors that accept permission name and/or arguments of a secured method. + * + * @see PermissionsAllowed#params() for more information about additional Permission arguments + */ + protected QuarkusPermission() { + this(QUARKUS_PERMISSION); + } + + /** + * Subclasses can declare constructors that accept permission name and/or arguments of a secured method. + * + * @see PermissionsAllowed#params() for more information about additional Permission arguments + */ + protected QuarkusPermission(String permissionName) { + super(permissionName); + } + + /** + * Determines whether access to secured resource should be granted. + * + * @param securityIdentity {@link SecurityIdentity} + * @return true if access should be granted and false otherwise + */ + public abstract boolean isGranted(SecurityIdentity securityIdentity); + + /** + * @throws IllegalStateException for this permission can only be set to the {@link PermissionsAllowed#permission()} + */ + @Override + public final boolean implies(Permission requiredPermission) { + // possessed permission implies required permission + // this is required permission, not the possessed one + throw new IllegalStateException("QuarkusPermission should never be assigned to a SecurityIdentity. " + + "This permission can only be set to the @PermissionsAllowed#permission attribute."); + } + + @Override + public String getActions() { + return ""; + } + + @Override + public boolean equals(Object object) { + return this == object; + } + + @Override + public int hashCode() { + return Objects.hash(toString()); + } + +}