This is an automated email from the ASF dual-hosted git repository.

papegaaij pushed a commit to branch WICKET-7200
in repository https://gitbox.apache.org/repos/asf/wicket.git

commit c8fa4527ab912662ff0578fb7c5c69ec370990bd
Author: Emond Papegaaij <[email protected]>
AuthorDate: Mon Aug 31 10:53:31 2026 +0200

    WICKET-7200 align class-vs-package resolution in the role annotations
    
    The five annotations in wicket-auth-roles implemented three different
    rules for combining a class level annotation with a package level one,
    and none of them were documented.
    
    Standardise on the rule that was affirmed in WICKET-3240: the rules on a
    class replace the rules on its package, and rules at the same level are
    combined with AND.
    
    - @AuthorizeAction and @AuthorizeActions now honour package level
      annotations, resolved per action name, so a class level rule for
      ENABLE leaves the package rule for RENDER in place. @AuthorizeActions
      gains ElementType.PACKAGE, without which a package cannot express more
      than one action rule.
    - @AuthorizeResource now replaces the annotation of its package instead
      of being AND-ed with it, which also removes an unguarded
      getPackage() dereference.
    - @AuthorizeInstantiations now participates in the override, and gains
      ElementType.PACKAGE.
    
    Document the resolution rules on AnnotationsRoleAuthorizationStrategy,
    on each of the annotations and in the user guide. The pitfall that
    prompted this is called out explicitly: because the annotations are
    @Inherited, an annotation on a superclass in another package counts as
    an annotation on the class and therefore suppresses the annotation of
    the subclass' own package.
    
    Add AnnotationsRolePackageTest, which pins each of these rules with real
    classes in real annotated packages. Mocks cannot be used for this, since
    a generated mock does not live in the package of the class it mocks.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
 .../AnnotationsRoleAuthorizationStrategy.java      | 320 ++++++++++++++-------
 .../role/annotations/AuthorizeAction.java          |  30 +-
 .../role/annotations/AuthorizeActions.java         |  18 +-
 .../role/annotations/AuthorizeInstantiation.java   |  11 +-
 .../role/annotations/AuthorizeInstantiations.java  |   9 +-
 .../role/annotations/AuthorizeResource.java        |  12 +-
 .../annotations/AnnotationsRolePackageTest.java    | 161 +++++++++++
 .../role/annotations/base/ActionAnnotatedBase.java |  34 +++
 .../role/annotations/base/AnnotatedBase.java       |  34 +++
 .../role/annotations/pkg/EnableOnlyComponent.java  |  34 +++
 .../annotations/pkg/InheritingActionComponent.java |  33 +++
 .../role/annotations/pkg/InheritingComponent.java  |  34 +++
 .../role/annotations/pkg/OptedOutComponent.java    |  34 +++
 .../role/annotations/pkg/OverridingComponent.java  |  34 +++
 .../role/annotations/pkg/OverridingResource.java   |  34 +++
 .../annotations/pkg/PackageProtectedResource.java  |  32 +++
 .../role/annotations/pkg/PlainComponent.java       |  32 +++
 .../role/annotations/pkg/RulesetComponent.java     |  36 +++
 .../role/annotations/pkg/package-info.java         |  29 ++
 .../role/annotations/pkg/sub/NestedComponent.java  |  33 +++
 .../src/main/asciidoc/security/security_2.adoc     |  60 +++-
 21 files changed, 927 insertions(+), 127 deletions(-)

diff --git 
a/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AnnotationsRoleAuthorizationStrategy.java
 
b/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AnnotationsRoleAuthorizationStrategy.java
index 96163e1549..43c26d77df 100644
--- 
a/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AnnotationsRoleAuthorizationStrategy.java
+++ 
b/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AnnotationsRoleAuthorizationStrategy.java
@@ -16,6 +16,12 @@
  */
 package org.apache.wicket.authroles.authorization.strategies.role.annotations;
 
+import java.lang.reflect.AnnotatedElement;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+
 import org.apache.wicket.Component;
 import org.apache.wicket.authorization.Action;
 import 
org.apache.wicket.authroles.authorization.strategies.role.AbstractRoleAuthorizationStrategy;
@@ -27,8 +33,85 @@ import org.apache.wicket.request.resource.IResource;
 
 
 /**
- * Strategy that checks the {@link AuthorizeInstantiation} annotation.
- * 
+ * Strategy that checks the role annotations in this package:
+ * <ul>
+ * <li>{@link AuthorizeInstantiation} and {@link AuthorizeInstantiations} 
guard the instantiation of
+ * a component,</li>
+ * <li>{@link AuthorizeAction} and {@link AuthorizeActions} guard an {@link 
Action} on a component
+ * instance, such as {@link org.apache.wicket.Component#RENDER} and
+ * {@link org.apache.wicket.Component#ENABLE},</li>
+ * <li>{@link AuthorizeResource} guards the request of a resource.</li>
+ * </ul>
+ * <p>
+ * Each of these annotations can be placed on a class or on a package. A 
package is annotated through
+ * its <code>package-info.java</code> file:
+ *
+ * <pre>
+ *  // only users with role ADMIN are allowed to create instances of pages in 
this package
+ *  &#064;AuthorizeInstantiation(&quot;ADMIN&quot;)
+ *  package com.example.admin;
+ *
+ *  import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation;
+ * </pre>
+ *
+ * <p>
+ * <b>Annotations on a class replace the annotations on its package.</b> The 
annotations on a package
+ * are a default that a class can override: when a class carries any 
annotation relevant to the check
+ * being performed, the annotations on its package are not consulted at all. 
Annotations at the same
+ * level are combined with AND, so every one of them has to grant access. For 
actions this is decided
+ * per action name, which means that a class restricting only 
<code>ENABLE</code> still inherits the
+ * <code>RENDER</code> restriction of its package. Within a single {@link 
AuthorizeAction},
+ * {@link AuthorizeAction#deny()} is evaluated before {@link 
AuthorizeAction#roles()}: a user holding
+ * a denied role is refused even when that user also holds an accepted one.
+ * <p>
+ * Because all of these annotations are {@link 
java.lang.annotation.Inherited}, the annotations
+ * &quot;on a class&quot; include those declared on any of its superclasses, 
in any package. That is
+ * worth spelling out, because it is easy to be caught by:
+ *
+ * <pre>
+ *  // com/example/base/SecuredPage.java
+ *  &#064;AuthorizeInstantiation(&quot;USER&quot;)
+ *  public class SecuredPage extends WebPage
+ *
+ *  // com/example/admin/package-info.java
+ *  &#064;AuthorizeInstantiation(&quot;ADMIN&quot;)
+ *  package com.example.admin;
+ *
+ *  // com/example/admin/ReportPage.java -- requires USER, not ADMIN!
+ *  public class ReportPage extends SecuredPage
+ * </pre>
+ *
+ * <p>
+ * <code>ReportPage</code> inherits 
<code>&#064;AuthorizeInstantiation(&quot;USER&quot;)</code> from
+ * its superclass, that inherited annotation counts as an annotation on the 
class, and therefore the
+ * ADMIN restriction of its own package is never applied. Annotate such a 
subclass explicitly to give
+ * it the roles of its package.
+ * <p>
+ * Further points to keep in mind when annotating packages:
+ * <ul>
+ * <li>A package annotation applies to that one package only. Java has no 
annotation inheritance
+ * between packages, so <code>com.example</code> does not pass its annotations 
on to
+ * <code>com.example.admin</code>.</li>
+ * <li>{@link java.lang.annotation.Inherited} has no meaning on a package, and 
inheritance between
+ * classes never crosses over to interfaces.</li>
+ * <li>The <code>package-info.java</code> file has to be compiled and shipped, 
and to be loaded by
+ * the same class loader as the classes of the package, or its annotations 
cannot be found at
+ * runtime.</li>
+ * <li>An annotation without any roles, such as 
<code>&#064;AuthorizeInstantiation()</code>,
+ * authorizes everybody. Because it still replaces the annotations on the 
package, it is the way to
+ * exempt a single class from the restrictions of its package.</li>
+ * </ul>
+ * <p>
+ * Note that
+ * {@link 
org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy}
+ * resolves its permissions quite differently: it looks up the exact component 
class and has no
+ * package or superclass fallback at all. When both strategies are combined, as
+ * {@link 
org.apache.wicket.authroles.authorization.strategies.role.RoleAuthorizationStrategy}
 does,
+ * they are combined with AND: both have to grant access.
+ *
+ * @see org.apache.wicket.authorization.IAuthorizationStrategy
+ * @see 
org.apache.wicket.authroles.authorization.strategies.role.RoleAuthorizationStrategy
+ *
  * @author Eelco Hillenius
  */
 public class AnnotationsRoleAuthorizationStrategy extends 
AbstractRoleAuthorizationStrategy
@@ -51,69 +134,18 @@ public class AnnotationsRoleAuthorizationStrategy extends 
AbstractRoleAuthorizat
        public <T extends IRequestableComponent> boolean 
isInstantiationAuthorized(
                final Class<T> componentClass)
        {
-               // We are authorized unless we are found not to be
-               boolean authorized = true;
-
-               // Check class annotation first because it is more specific 
than package annotation
-               final AuthorizeInstantiation classAnnotation = 
componentClass.getAnnotation(AuthorizeInstantiation.class);
-               if (classAnnotation != null)
-               {
-                       authorized = check(classAnnotation);
-               }
-               else
-               {
-                       // Check package annotation if there is no one on the 
the class
-                       final Package componentPackage = 
componentClass.getPackage();
-                       if (componentPackage != null)
-                       {
-                               final AuthorizeInstantiation packageAnnotation 
= componentPackage.getAnnotation(AuthorizeInstantiation.class);
-                               if (packageAnnotation != null)
-                               {
-                                       authorized = check(packageAnnotation);
-                               }
-                       }
-               }
-
-               // Check for multiple instantiations
-               final AuthorizeInstantiations authorizeInstantiationsAnnotation 
= componentClass
-                       .getAnnotation(AuthorizeInstantiations.class);
-               if (authorizeInstantiationsAnnotation != null)
+               for (final AuthorizeInstantiation rule : resolve(componentClass,
+                       
AnnotationsRoleAuthorizationStrategy::instantiationRules))
                {
-                       for (final AuthorizeInstantiation 
authorizeInstantiationAnnotation : authorizeInstantiationsAnnotation
-                               .ruleset())
+                       if (!hasAny(new Roles(rule.value())))
                        {
-                               if (!check(authorizeInstantiationAnnotation))
-                               {
-                                       authorized = false;
-                               }
+                               return false;
                        }
                }
 
-               return authorized;
+               return true;
        }
 
-       /**
-        * Check if annotated instantiation is allowed.
-        * 
-        * @param authorizeInstantiationAnnotation
-        *            The annotations information
-        * @return False if the instantiation is not authorized
-        */
-       private <T extends IRequestableComponent> boolean check(
-               final AuthorizeInstantiation authorizeInstantiationAnnotation)
-       {
-               // We are authorized unless we are found not to be
-               boolean authorized = true;
-
-               // Check class annotation first because it is more specific 
than package annotation
-               if (authorizeInstantiationAnnotation != null)
-               {
-                       authorized = hasAny(new 
Roles(authorizeInstantiationAnnotation.value()));
-               }
-
-               return authorized;
-       }
-       
        /**
         * @see 
org.apache.wicket.authorization.IAuthorizationStrategy#isActionAuthorized(org.apache.wicket.Component,
         *      org.apache.wicket.authorization.Action)
@@ -127,24 +159,46 @@ public class AnnotationsRoleAuthorizationStrategy extends 
AbstractRoleAuthorizat
                return isActionAuthorized(componentClass, action);
        }
 
+       /**
+        * Checks whether the given action is authorized for the given 
component class. Rules are
+        * resolved per action: the rules on the class replace the rules of its 
package only for the
+        * action they name.
+        * 
+        * @param componentClass
+        *            the class of the component the action is performed on
+        * @param action
+        *            the action to check
+        * @return false if the action is not authorized
+        */
        protected boolean isActionAuthorized(final Class<?> componentClass, 
final Action action)
        {
-               // Check for a single action
-               if (!check(action, 
componentClass.getAnnotation(AuthorizeAction.class)))
+               for (final AuthorizeAction rule : resolve(componentClass,
+                       element -> actionRules(element, action)))
                {
-                       return false;
+                       final Roles deniedRoles = new Roles(rule.deny());
+                       if (isEmpty(deniedRoles) == false && 
hasAny(deniedRoles))
+                       {
+                               return false;
+                       }
+
+                       if (!hasAny(new Roles(rule.roles())))
+                       {
+                               return false;
+                       }
                }
 
-               // Check for multiple actions
-               final AuthorizeActions authorizeActionsAnnotation = 
componentClass.getAnnotation(AuthorizeActions.class);
-               if (authorizeActionsAnnotation != null)
+               return true;
+       }
+
+       @Override
+       public boolean isResourceAuthorized(IResource resource, PageParameters 
pageParameters)
+       {
+               for (final AuthorizeResource rule : resolve(resource.getClass(),
+                       AnnotationsRoleAuthorizationStrategy::resourceRules))
                {
-                       for (final AuthorizeAction authorizeActionAnnotation : 
authorizeActionsAnnotation.actions())
+                       if (!hasAny(new Roles(rule.value())))
                        {
-                               if (!check(action, authorizeActionAnnotation))
-                               {
-                                       return false;
-                               }
+                               return false;
                        }
                }
 
@@ -152,56 +206,114 @@ public class AnnotationsRoleAuthorizationStrategy 
extends AbstractRoleAuthorizat
        }
 
        /**
-        * @param action
-        *            The action to check
-        * @param authorizeActionAnnotation
-        *            The annotations information
-        * @return False if the action is not authorized
+        * Resolves the rules that apply to the given class: the rules declared 
on the class itself if
+        * there are any, and the rules declared on its package otherwise. 
Since all annotations in this
+        * package are {@link java.lang.annotation.Inherited}, the rules 
declared on the class include
+        * those it inherits from a superclass in another package, and such an 
inherited rule therefore
+        * suppresses the rules of the class' own package.
+        * 
+        * @param annotatedClass
+        *            the component or resource class to resolve the rules for
+        * @param rules
+        *            extracts the rules relevant to the check being performed 
from a class or a package
+        * @return the rules that apply, empty if the class is not restricted
         */
-       private boolean check(final Action action, final AuthorizeAction 
authorizeActionAnnotation)
+       private static <R> List<R> resolve(final Class<?> annotatedClass,
+               final Function<AnnotatedElement, List<R>> rules)
        {
-               if (authorizeActionAnnotation != null)
+               List<R> resolved = rules.apply(annotatedClass);
+
+               if (resolved.isEmpty())
                {
-                       if 
(action.getName().equals(authorizeActionAnnotation.action()))
+                       // Only fall back to the package when the class itself 
says nothing
+                       final Package annotatedPackage = 
annotatedClass.getPackage();
+                       if (annotatedPackage != null)
                        {
-                               Roles deniedRoles = new 
Roles(authorizeActionAnnotation.deny());
-                               if (isEmpty(deniedRoles) == false && 
hasAny(deniedRoles))
-                               {
-                                       return false;
-                               }
-
-                               Roles acceptedRoles = new 
Roles(authorizeActionAnnotation.roles());
-                               if (!hasAny(acceptedRoles))
-                               {
-                                       return false;
-                               }
+                               resolved = rules.apply(annotatedPackage);
                        }
                }
-               return true;
+
+               return resolved;
        }
 
-       @Override
-       public boolean isResourceAuthorized(IResource resource, PageParameters 
pageParameters)
+       /**
+        * @param element
+        *            a class or a package
+        * @return the instantiation rules declared on the given element, both 
the single
+        *         {@link AuthorizeInstantiation} and the {@link 
AuthorizeInstantiations} ruleset
+        */
+       private static List<AuthorizeInstantiation> instantiationRules(final 
AnnotatedElement element)
        {
-               Class<? extends IResource> resourceClass = resource.getClass();
-               boolean allowedByResourceItself = isResourceAnnotationSatisfied(
-                               
resourceClass.getAnnotation(AuthorizeResource.class));
-               boolean allowedByPackage = isResourceAnnotationSatisfied(
-                               
resourceClass.getPackage().getAnnotation(AuthorizeResource.class));
-               return allowedByResourceItself && allowedByPackage;
+               final AuthorizeInstantiation single = 
element.getAnnotation(AuthorizeInstantiation.class);
+               final AuthorizeInstantiations ruleset = 
element.getAnnotation(AuthorizeInstantiations.class);
+
+               if (single == null && ruleset == null)
+               {
+                       return Collections.emptyList();
+               }
+
+               final List<AuthorizeInstantiation> rules = new ArrayList<>();
+               if (single != null)
+               {
+                       rules.add(single);
+               }
+               if (ruleset != null)
+               {
+                       Collections.addAll(rules, ruleset.ruleset());
+               }
+
+               return rules;
        }
 
-       private boolean isResourceAnnotationSatisfied(AuthorizeResource 
annotation)
+       /**
+        * @param element
+        *            a class or a package
+        * @param action
+        *            the action being checked
+        * @return the rules declared on the given element that apply to the 
given action, both the
+        *         single {@link AuthorizeAction} and the ones grouped by 
{@link AuthorizeActions}
+        */
+       private static List<AuthorizeAction> actionRules(final AnnotatedElement 
element,
+               final Action action)
        {
-               if (annotation != null)
+               final AuthorizeAction single = 
element.getAnnotation(AuthorizeAction.class);
+               final AuthorizeActions grouped = 
element.getAnnotation(AuthorizeActions.class);
+
+               if (grouped == null)
                {
-                       // we have an annotation => we must check for the 
required roles
-                       return hasAny(new Roles(annotation.value()));
+                       // The common case: at most one annotation, so avoid 
building a list for it
+                       if (single != null && 
action.getName().equals(single.action()))
+                       {
+                               return Collections.singletonList(single);
+                       }
+                       return Collections.emptyList();
                }
-               else
+
+               final List<AuthorizeAction> rules = new ArrayList<>();
+               if (single != null && action.getName().equals(single.action()))
                {
-                       // no annotation => no required roles => this resource 
can be accessed
-                       return true;
+                       rules.add(single);
                }
+               for (final AuthorizeAction rule : grouped.actions())
+               {
+                       if (action.getName().equals(rule.action()))
+                       {
+                               rules.add(rule);
+                       }
+               }
+
+               return rules;
+       }
+
+       /**
+        * @param element
+        *            a class or a package
+        * @return the {@link AuthorizeResource} declared on the given element, 
if any
+        */
+       private static List<AuthorizeResource> resourceRules(final 
AnnotatedElement element)
+       {
+               final AuthorizeResource rule = 
element.getAnnotation(AuthorizeResource.class);
+
+               return rule != null ? Collections.singletonList(rule) : 
Collections.emptyList();
        }
 }
diff --git 
a/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AuthorizeAction.java
 
b/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AuthorizeAction.java
index 87610c9ce7..f2b72a8d28 100644
--- 
a/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AuthorizeAction.java
+++ 
b/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AuthorizeAction.java
@@ -24,8 +24,21 @@ import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
 /**
- * A mapping of 1..n roles to an action. This annotions must be embedded in the
- * {@link AuthorizeActions} annotation.
+ * A mapping of 1..n roles to an action. It can be used on its own, and it can 
be grouped with other
+ * actions in an {@link AuthorizeActions} annotation when more than one action 
has to be restricted.
+ * 
+ * <pre>
+ * // a panel that only users with role ADMIN are allowed to see
+ * &#064;AuthorizeAction(action = &quot;RENDER&quot;, roles = 
&quot;ADMIN&quot;)
+ * public class ForAdmins extends Panel
+ * </pre>
+ * 
+ * It can be placed on a class or on a package, the latter by specifying it in 
the
+ * <code>package-info.java</code> file of that package. Restrictions are 
resolved per action, so an
+ * annotation on a class replaces the annotations of its package only for the 
action it names: a class
+ * restricting <code>ENABLE</code> still inherits the <code>RENDER</code> 
restriction of its package.
+ * See {@link AnnotationsRoleAuthorizationStrategy} for the complete 
resolution rules and for the
+ * pitfalls of annotating packages.
  * 
  * @see org.apache.wicket.authorization.IAuthorizationStrategy
  * @see AnnotationsRoleAuthorizationStrategy
@@ -42,7 +55,7 @@ public @interface AuthorizeAction {
 
        /**
         * The action that is allowed. The default actions that are supported 
by Wicket are
-        * <code>RENDER</code> and <code>ENABLE<code> as defined as constants
+        * <code>RENDER</code> and <code>ENABLE</code> as defined as constants
         * of {@link org.apache.wicket.Component}.
         * 
         * @see org.apache.wicket.Component#RENDER
@@ -55,16 +68,17 @@ public @interface AuthorizeAction {
        /**
         * The roles for this action.
         * 
-        * @return the roles for this action. The default is an empty string 
(annotations do not allow
-        *         null default values)
+        * @return the roles for this action. The default is a zero length 
array (annotations do not
+        *         allow null default values), which allows the action for 
everybody
         */
        String[] roles() default { };
 
        /**
-        * The roles to deny for this action.
+        * The roles to deny for this action. Denying takes precedence over 
allowing: a user holding one
+        * of these roles is refused even when that user also holds one of the 
{@link #roles()}.
         * 
-        * @return the roles to deny for this action. The default is an empty 
string (annotations do not
-        *         allow null default values)
+        * @return the roles to deny for this action. The default is a zero 
length array (annotations do
+        *         not allow null default values), which denies the action for 
nobody
         */
        String[] deny() default { };
 }
diff --git 
a/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AuthorizeActions.java
 
b/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AuthorizeActions.java
index d463121689..8b2f042d6e 100644
--- 
a/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AuthorizeActions.java
+++ 
b/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AuthorizeActions.java
@@ -24,12 +24,16 @@ import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
 /**
- * Groups a set (technically an array) of {@link AuthorizeAction}s for 
authorization. This
- * annotation works on a class level, and can be used like this:
+ * Groups a set (technically an array) of {@link AuthorizeAction}s for 
authorization, for when more
+ * than one action has to be restricted. A single action does not need this 
annotation and can be
+ * declared with {@link AuthorizeAction} directly. This annotation can be used 
like this:
  * 
  * <pre>
- * // A panel that is only visible for users with role ADMIN
- * &#064;AuthorizeAction(action = &quot;RENDER&quot;, roles = { 
&quot;ADMIN&quot;, &quot;USER&quot; })
+ * // a panel that users with role ADMIN and USER are allowed to see, but that 
only
+ * // users with role ADMIN are allowed to interact with
+ * &#064;AuthorizeActions(actions = {
+ *             &#064;AuthorizeAction(action = &quot;RENDER&quot;, roles = { 
&quot;ADMIN&quot;, &quot;USER&quot; }),
+ *             &#064;AuthorizeAction(action = &quot;ENABLE&quot;, roles = 
&quot;ADMIN&quot;) })
  * public class ForAdminsAndUsers extends Panel
  * {
  *     public ForAdminsAndUsers(String id)
@@ -39,6 +43,10 @@ import java.lang.annotation.Target;
  * }
  * </pre>
  * 
+ * It can be placed on a class or on a package, the latter by specifying it in 
the
+ * <code>package-info.java</code> file of that package. Restrictions are 
resolved per action; see
+ * {@link AnnotationsRoleAuthorizationStrategy} for the complete resolution 
rules.
+ * 
  * @see org.apache.wicket.authorization.IAuthorizationStrategy
  * @see AnnotationsRoleAuthorizationStrategy
  * @see AuthorizeAction
@@ -46,7 +54,7 @@ import java.lang.annotation.Target;
  * @author Eelco Hillenius
  */
 @Retention(RetentionPolicy.RUNTIME)
-@Target({ ElementType.TYPE })
+@Target({ ElementType.PACKAGE, ElementType.TYPE })
 @Documented
 @Inherited
 public @interface AuthorizeActions {
diff --git 
a/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AuthorizeInstantiation.java
 
b/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AuthorizeInstantiation.java
index f2cf20decb..0f68d8777f 100644
--- 
a/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AuthorizeInstantiation.java
+++ 
b/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AuthorizeInstantiation.java
@@ -44,10 +44,18 @@ import java.lang.annotation.Target;
  *  import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation;
  * </pre>
  *
+ * An annotation on a class replaces the annotations on its package, rather 
than adding to them. Since
+ * this annotation is {@link Inherited}, that also holds for an annotation 
inherited from a superclass
+ * in another package: such an inherited annotation suppresses the annotation 
of the subclass' own
+ * package. See {@link AnnotationsRoleAuthorizationStrategy} for the complete 
resolution rules and for
+ * the pitfalls of annotating packages.
+ *
  * @see org.apache.wicket.authorization.IAuthorizationStrategy
  * @see AnnotationsRoleAuthorizationStrategy
+ * @see AuthorizeInstantiations
  * @see AuthorizeActions
  * @see AuthorizeAction
+ * @see AuthorizeResource
  *
  * @author Eelco hillenius
  */
@@ -60,7 +68,8 @@ public @interface AuthorizeInstantiation {
        /**
         * Gets the roles that are allowed to take the action.
         *
-        * @return the roles that are allowed. Returns a zero length array by 
default
+        * @return the roles that are allowed. Returns a zero length array by 
default, which authorizes
+        *         everybody
         */
        String[] value() default { };
 }
diff --git 
a/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AuthorizeInstantiations.java
 
b/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AuthorizeInstantiations.java
index de708a7d89..c55694ffef 100644
--- 
a/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AuthorizeInstantiations.java
+++ 
b/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AuthorizeInstantiations.java
@@ -43,14 +43,19 @@ import java.lang.annotation.Target;
  * }
  * </pre>
  * 
+ * It can be placed on a package as well, by specifying it in the 
<code>package-info.java</code> file
+ * of that package. A ruleset on a class replaces the instantiation rules of 
its package, just like a
+ * single {@link AuthorizeInstantiation} does; see {@link 
AnnotationsRoleAuthorizationStrategy} for
+ * the complete resolution rules.
+ * 
  * @see org.apache.wicket.authorization.IAuthorizationStrategy
  * @see AnnotationsRoleAuthorizationStrategy
  * @see AuthorizeInstantiation
- * @see AuthorizeInstantiations
+ * @see AuthorizeResource
  * @author René Dieckmann ([email protected])
  */
 @Retention(RetentionPolicy.RUNTIME)
-@Target({ ElementType.TYPE })
+@Target({ ElementType.PACKAGE, ElementType.TYPE })
 @Documented
 @Inherited
 public @interface AuthorizeInstantiations {
diff --git 
a/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AuthorizeResource.java
 
b/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AuthorizeResource.java
index 9162f51a6b..dd7b631e6d 100644
--- 
a/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AuthorizeResource.java
+++ 
b/wicket-auth-roles/src/main/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AuthorizeResource.java
@@ -24,12 +24,17 @@ import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
 /**
- * Annotation for configuring what roles are allowed for requesting the 
annotated resource. It works analogously
- * to {@link 
org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation}.
+ * Annotation for configuring what roles are allowed for requesting the 
annotated resource. It can be
+ * placed on a class or on a package, the latter by specifying it in the
+ * <code>package-info.java</code> file of that package, and it is resolved 
just like
+ * {@link AuthorizeInstantiation}: an annotation on a resource class replaces 
the annotation of its
+ * package rather than adding to it. See {@link 
AnnotationsRoleAuthorizationStrategy} for the complete
+ * resolution rules and for the pitfalls of annotating packages.
  *
  * @author Carl-Eric Menzel
  * @see org.apache.wicket.authorization.IAuthorizationStrategy
  * @see AnnotationsRoleAuthorizationStrategy
+ * @see AuthorizeInstantiation
  */
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.PACKAGE, ElementType.TYPE})
@@ -41,7 +46,8 @@ public @interface AuthorizeResource
        /**
         * Gets the roles that are allowed to take the action.
         *
-        * @return the roles that are allowed. Returns a zero length array by 
default
+        * @return the roles that are allowed. Returns a zero length array by 
default, which authorizes
+        *         everybody
         */
        String[] value() default {};
 }
diff --git 
a/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AnnotationsRolePackageTest.java
 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AnnotationsRolePackageTest.java
new file mode 100644
index 0000000000..c3911db917
--- /dev/null
+++ 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/AnnotationsRolePackageTest.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.authroles.authorization.strategies.role.annotations;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.wicket.Component;
+import 
org.apache.wicket.authroles.authorization.strategies.role.IRoleCheckingStrategy;
+import org.apache.wicket.authroles.authorization.strategies.role.Roles;
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.pkg.EnableOnlyComponent;
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.pkg.InheritingActionComponent;
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.pkg.InheritingComponent;
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.pkg.OptedOutComponent;
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.pkg.OverridingComponent;
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.pkg.OverridingResource;
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.pkg.PackageProtectedResource;
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.pkg.PlainComponent;
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.pkg.RulesetComponent;
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.pkg.sub.NestedComponent;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests how {@link AnnotationsRoleAuthorizationStrategy} resolves annotations 
on a package against
+ * annotations on a class. The fixtures are real classes in real packages 
rather than mocks, because a
+ * mock does not live in the package of the class it mocks.
+ * <p>
+ * Package <code>pkg</code> restricts instantiation to <code>role1</code>, 
RENDER to
+ * <code>role1</code> and resources to <code>role1</code>; package 
<code>base</code> and package
+ * <code>pkg.sub</code> carry no annotations at all.
+ */
+class AnnotationsRolePackageTest
+{
+       @Test
+       void packageAnnotationAppliesToClassWithoutOneOfItsOwn()
+       {
+               
assertTrue(strategy("role1").isInstantiationAuthorized(PlainComponent.class));
+               
assertFalse(strategy("role2").isInstantiationAuthorized(PlainComponent.class));
+       }
+
+       @Test
+       void classAnnotationReplacesPackageAnnotation()
+       {
+               
assertTrue(strategy("role2").isInstantiationAuthorized(OverridingComponent.class));
+               
assertFalse(strategy("role1").isInstantiationAuthorized(OverridingComponent.class));
+       }
+
+       @Test
+       void classAnnotationWithoutRolesExemptsFromPackageAnnotation()
+       {
+               
assertTrue(strategy().isInstantiationAuthorized(OptedOutComponent.class));
+       }
+
+       @Test
+       void classRulesetReplacesPackageAnnotation()
+       {
+               assertTrue(strategy("role2", 
"role3").isInstantiationAuthorized(RulesetComponent.class));
+               
assertFalse(strategy("role2").isInstantiationAuthorized(RulesetComponent.class));
+               
assertFalse(strategy("role1").isInstantiationAuthorized(RulesetComponent.class));
+       }
+
+       /**
+        * The annotations are {@link java.lang.annotation.Inherited}, so an 
annotation on a superclass in
+        * another package counts as an annotation on the class and suppresses 
the annotation of the
+        * class' own package.
+        */
+       @Test
+       void inheritedClassAnnotationSuppressesPackageAnnotation()
+       {
+               
assertTrue(strategy("role2").isInstantiationAuthorized(InheritingComponent.class));
+               
assertFalse(strategy("role1").isInstantiationAuthorized(InheritingComponent.class));
+       }
+
+       /**
+        * Java has no annotation inheritance between packages.
+        */
+       @Test
+       void packageAnnotationDoesNotApplyToSubpackage()
+       {
+               
assertTrue(strategy().isInstantiationAuthorized(NestedComponent.class));
+               assertTrue(strategy().isActionAuthorized(NestedComponent.class, 
Component.RENDER));
+       }
+
+       @Test
+       void packageAnnotationAppliesToActionOfClassWithoutOneOfItsOwn()
+       {
+               
assertTrue(strategy("role1").isActionAuthorized(PlainComponent.class, 
Component.RENDER));
+               
assertFalse(strategy("role2").isActionAuthorized(PlainComponent.class, 
Component.RENDER));
+       }
+
+       /**
+        * Actions are resolved per action name, so restricting one action on 
the class leaves the rule of
+        * the package for another action in place.
+        */
+       @Test
+       void classActionAnnotationOnlyReplacesThePackageRuleForTheSameAction()
+       {
+               
assertTrue(strategy("role2").isActionAuthorized(EnableOnlyComponent.class, 
Component.ENABLE));
+               
assertFalse(strategy("role1").isActionAuthorized(EnableOnlyComponent.class, 
Component.ENABLE));
+
+               
assertTrue(strategy("role1").isActionAuthorized(EnableOnlyComponent.class, 
Component.RENDER));
+               
assertFalse(strategy("role2").isActionAuthorized(EnableOnlyComponent.class, 
Component.RENDER));
+       }
+
+       @Test
+       void inheritedActionAnnotationSuppressesPackageAnnotation()
+       {
+               assertTrue(
+                       
strategy("role2").isActionAuthorized(InheritingActionComponent.class, 
Component.RENDER));
+               assertFalse(
+                       
strategy("role1").isActionAuthorized(InheritingActionComponent.class, 
Component.RENDER));
+       }
+
+       @Test
+       void packageAnnotationAppliesToResourceWithoutOneOfItsOwn()
+       {
+               assertTrue(strategy("role1").isResourceAuthorized(new 
PackageProtectedResource(), null));
+               assertFalse(strategy("role2").isResourceAuthorized(new 
PackageProtectedResource(), null));
+       }
+
+       /**
+        * Resources are resolved like everything else: the annotation on the 
class replaces the one on
+        * the package instead of being combined with it.
+        */
+       @Test
+       void classAnnotationReplacesPackageAnnotationForResources()
+       {
+               assertTrue(strategy("role2").isResourceAuthorized(new 
OverridingResource(), null));
+               assertFalse(strategy("role1").isResourceAuthorized(new 
OverridingResource(), null));
+       }
+
+       /**
+        * Create a strategy whose role checker is given a list of roles and 
returns true if that list
+        * contains any of the asked-for roles.
+        * 
+        * @param availableRoles
+        *            the roles the current user has
+        * @return the strategy to test
+        */
+       private AnnotationsRoleAuthorizationStrategy strategy(final String... 
availableRoles)
+       {
+               IRoleCheckingStrategy roleChecker = requiredRoles -> 
requiredRoles
+                       .hasAnyRole(new Roles(availableRoles));
+
+               return new AnnotationsRoleAuthorizationStrategy(roleChecker);
+       }
+}
diff --git 
a/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/base/ActionAnnotatedBase.java
 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/base/ActionAnnotatedBase.java
new file mode 100644
index 0000000000..fa8712d97e
--- /dev/null
+++ 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/base/ActionAnnotatedBase.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package 
org.apache.wicket.authroles.authorization.strategies.role.annotations.base;
+
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeAction;
+import org.apache.wicket.markup.html.WebComponent;
+
+/**
+ * A superclass restricting RENDER, in a package that carries no annotations 
of its own.
+ */
+@AuthorizeAction(action = "RENDER", roles = "role2")
+public class ActionAnnotatedBase extends WebComponent
+{
+       private static final long serialVersionUID = 1L;
+
+       public ActionAnnotatedBase()
+       {
+               super("notUsed");
+       }
+}
diff --git 
a/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/base/AnnotatedBase.java
 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/base/AnnotatedBase.java
new file mode 100644
index 0000000000..3ce4e82b07
--- /dev/null
+++ 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/base/AnnotatedBase.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package 
org.apache.wicket.authroles.authorization.strategies.role.annotations.base;
+
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation;
+import org.apache.wicket.markup.html.WebComponent;
+
+/**
+ * An annotated superclass in a package that carries no annotations of its own.
+ */
+@AuthorizeInstantiation("role2")
+public class AnnotatedBase extends WebComponent
+{
+       private static final long serialVersionUID = 1L;
+
+       public AnnotatedBase()
+       {
+               super("notUsed");
+       }
+}
diff --git 
a/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/EnableOnlyComponent.java
 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/EnableOnlyComponent.java
new file mode 100644
index 0000000000..4af0d8f746
--- /dev/null
+++ 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/EnableOnlyComponent.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package 
org.apache.wicket.authroles.authorization.strategies.role.annotations.pkg;
+
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeAction;
+import org.apache.wicket.markup.html.WebComponent;
+
+/**
+ * Restricts ENABLE itself, and so still inherits the RENDER rule of its 
package.
+ */
+@AuthorizeAction(action = "ENABLE", roles = "role2")
+public class EnableOnlyComponent extends WebComponent
+{
+       private static final long serialVersionUID = 1L;
+
+       public EnableOnlyComponent()
+       {
+               super("notUsed");
+       }
+}
diff --git 
a/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/InheritingActionComponent.java
 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/InheritingActionComponent.java
new file mode 100644
index 0000000000..5d671ca0a8
--- /dev/null
+++ 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/InheritingActionComponent.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package 
org.apache.wicket.authroles.authorization.strategies.role.annotations.pkg;
+
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.base.ActionAnnotatedBase;
+
+/**
+ * Inherits its RENDER rule from a superclass in another package, which 
suppresses the RENDER rule
+ * of this component's own package.
+ */
+public class InheritingActionComponent extends ActionAnnotatedBase
+{
+       private static final long serialVersionUID = 1L;
+
+       public InheritingActionComponent()
+       {
+               super();
+       }
+}
diff --git 
a/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/InheritingComponent.java
 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/InheritingComponent.java
new file mode 100644
index 0000000000..a7b4c443a4
--- /dev/null
+++ 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/InheritingComponent.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package 
org.apache.wicket.authroles.authorization.strategies.role.annotations.pkg;
+
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.base.AnnotatedBase;
+
+/**
+ * Inherits its instantiation rule from a superclass in another package. 
Because the annotation is
+ * inherited, it counts as an annotation on this class, and the rule of this 
component's own package
+ * is therefore never applied.
+ */
+public class InheritingComponent extends AnnotatedBase
+{
+       private static final long serialVersionUID = 1L;
+
+       public InheritingComponent()
+       {
+               super();
+       }
+}
diff --git 
a/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/OptedOutComponent.java
 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/OptedOutComponent.java
new file mode 100644
index 0000000000..4ab3039e9b
--- /dev/null
+++ 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/OptedOutComponent.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package 
org.apache.wicket.authroles.authorization.strategies.role.annotations.pkg;
+
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation;
+import org.apache.wicket.markup.html.WebComponent;
+
+/**
+ * Exempts itself from the instantiation rule of its package by requiring no 
roles at all.
+ */
+@AuthorizeInstantiation()
+public class OptedOutComponent extends WebComponent
+{
+       private static final long serialVersionUID = 1L;
+
+       public OptedOutComponent()
+       {
+               super("notUsed");
+       }
+}
diff --git 
a/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/OverridingComponent.java
 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/OverridingComponent.java
new file mode 100644
index 0000000000..0021461edb
--- /dev/null
+++ 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/OverridingComponent.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package 
org.apache.wicket.authroles.authorization.strategies.role.annotations.pkg;
+
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation;
+import org.apache.wicket.markup.html.WebComponent;
+
+/**
+ * Replaces the instantiation rule of its package with one of its own.
+ */
+@AuthorizeInstantiation("role2")
+public class OverridingComponent extends WebComponent
+{
+       private static final long serialVersionUID = 1L;
+
+       public OverridingComponent()
+       {
+               super("notUsed");
+       }
+}
diff --git 
a/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/OverridingResource.java
 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/OverridingResource.java
new file mode 100644
index 0000000000..f62941a6f2
--- /dev/null
+++ 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/OverridingResource.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package 
org.apache.wicket.authroles.authorization.strategies.role.annotations.pkg;
+
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeResource;
+import org.apache.wicket.request.resource.IResource;
+
+/**
+ * Replaces the AuthorizeResource of its package with one of its own.
+ */
+@AuthorizeResource("role2")
+public class OverridingResource implements IResource
+{
+       private static final long serialVersionUID = 1L;
+
+       @Override
+       public void respond(Attributes attributes)
+       {
+       }
+}
diff --git 
a/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/PackageProtectedResource.java
 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/PackageProtectedResource.java
new file mode 100644
index 0000000000..672817d302
--- /dev/null
+++ 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/PackageProtectedResource.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package 
org.apache.wicket.authroles.authorization.strategies.role.annotations.pkg;
+
+import org.apache.wicket.request.resource.IResource;
+
+/**
+ * Carries no annotation of its own, so the AuthorizeResource of its package 
applies.
+ */
+public class PackageProtectedResource implements IResource
+{
+       private static final long serialVersionUID = 1L;
+
+       @Override
+       public void respond(Attributes attributes)
+       {
+       }
+}
diff --git 
a/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/PlainComponent.java
 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/PlainComponent.java
new file mode 100644
index 0000000000..ce95f37a8c
--- /dev/null
+++ 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/PlainComponent.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package 
org.apache.wicket.authroles.authorization.strategies.role.annotations.pkg;
+
+import org.apache.wicket.markup.html.WebComponent;
+
+/**
+ * Carries no annotation of its own, so the annotations of its package apply.
+ */
+public class PlainComponent extends WebComponent
+{
+       private static final long serialVersionUID = 1L;
+
+       public PlainComponent()
+       {
+               super("notUsed");
+       }
+}
diff --git 
a/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/RulesetComponent.java
 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/RulesetComponent.java
new file mode 100644
index 0000000000..b8b71f825a
--- /dev/null
+++ 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/RulesetComponent.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package 
org.apache.wicket.authroles.authorization.strategies.role.annotations.pkg;
+
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation;
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiations;
+import org.apache.wicket.markup.html.WebComponent;
+
+/**
+ * Replaces the instantiation rule of its package with a combined ruleset.
+ */
+@AuthorizeInstantiations(ruleset = { @AuthorizeInstantiation("role2"),
+               @AuthorizeInstantiation("role3") })
+public class RulesetComponent extends WebComponent
+{
+       private static final long serialVersionUID = 1L;
+
+       public RulesetComponent()
+       {
+               super("notUsed");
+       }
+}
diff --git 
a/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/package-info.java
 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/package-info.java
new file mode 100644
index 0000000000..d79b26c256
--- /dev/null
+++ 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Fixture package for {@link 
org.apache.wicket.authroles.authorization.strategies.role.annotations.AnnotationsRolePackageTest}:
+ * every kind of role annotation, declared on the package. Note the order of 
the declarations: the
+ * package annotations precede the package declaration, and the imports they 
use follow it.
+ */
+@AuthorizeInstantiation("role1")
+@AuthorizeAction(action = "RENDER", roles = "role1")
+@AuthorizeResource("role1")
+package 
org.apache.wicket.authroles.authorization.strategies.role.annotations.pkg;
+
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeAction;
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation;
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeResource;
diff --git 
a/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/sub/NestedComponent.java
 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/sub/NestedComponent.java
new file mode 100644
index 0000000000..5ef0ce791c
--- /dev/null
+++ 
b/wicket-auth-roles/src/test/java/org/apache/wicket/authroles/authorization/strategies/role/annotations/pkg/sub/NestedComponent.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package 
org.apache.wicket.authroles.authorization.strategies.role.annotations.pkg.sub;
+
+import org.apache.wicket.markup.html.WebComponent;
+
+/**
+ * Lives in a subpackage of an annotated package, which has no 
package-info.java of its own. Java
+ * has no annotation inheritance between packages, so nothing restricts this 
component.
+ */
+public class NestedComponent extends WebComponent
+{
+       private static final long serialVersionUID = 1L;
+
+       public NestedComponent()
+       {
+               super("notUsed");
+       }
+}
diff --git a/wicket-user-guide/src/main/asciidoc/security/security_2.adoc 
b/wicket-user-guide/src/main/asciidoc/security/security_2.adoc
index 5bc1b116ae..9445a8c7c4 100644
--- a/wicket-user-guide/src/main/asciidoc/security/security_2.adoc
+++ b/wicket-user-guide/src/main/asciidoc/security/security_2.adoc
@@ -116,11 +116,11 @@ class BasicAuthenticationRolesSession extends 
AuthenticatedWebSession {
 }
 ----
 
-Roles can be adopted to apply security restrictions on our pages and 
components. This can be done  using one of the two built-in authorization 
strategies that extend super class _AbstractRoleAuthorizationStrategyWicket_: 
_MetaDataRoleAuthorizationStrategy_ and _AnnotationsRoleAuthorizationStrategy_
+Roles can be adopted to apply security restrictions on our pages and 
components. This can be done  using one of the two built-in authorization 
strategies that extend super class _AbstractRoleAuthorizationStrategy_: 
_MetaDataRoleAuthorizationStrategy_ and _AnnotationsRoleAuthorizationStrategy_
 
 The difference between these two strategies is that 
_MetaDataRoleAuthorizationStrategy_ handles role-based authorizations with 
Wicket metadata while _AnnotationsRoleAuthorizationStrategy_ uses Java 
annotations.
 
-NOTE: Application class _AuthenticatedWebApplication_ already sets 
_MetaDataRoleAuthorizationStrategy_ and _AnnotationsRoleAuthorizationStrategy_ 
as its own authorization strategies (it uses a compound strategy as we will see 
in <<security.adoc#_authorizations,paragraph 22.2>>).
+NOTE: Application class _AuthenticatedWebApplication_ already sets 
_MetaDataRoleAuthorizationStrategy_ and _AnnotationsRoleAuthorizationStrategy_ 
as its own authorization strategies (it uses a compound strategy, as we will 
see in <<_strategy_roleauthorizationstrategy,the last paragraph of this 
chapter>>).
 
 The code that we will see in the next examples is for illustrative purpose 
only. If our application class inherits from _AuthenticatedWebApplication_ we 
won't need to configure anything to use these two strategies.
 
@@ -203,7 +203,7 @@ public class BasicAuthenticationSession extends 
AuthenticatedWebSession {
 
 The code that instantiates _MetaDataRoleAuthorizationStrategy_ and set it as 
application's strategy is inside application class method _init()_.
 
-Any subclass of _AbstractRoleAuthorizationStrategyWicket_ needs an 
implementation of interface _IRoleCheckingStrategy_ to be instantiated. For 
this purpose in the code above we used the application class itself because its 
base class _AuthenticatedWebApplication_ already implements interface 
_IRoleCheckingStrategy_. By default _AuthenticatedWebApplication_ checks for 
authorizations using the roles returned by the current 
_AbstractAuthenticatedWebSession_. As final step inside init we gra [...]
+Any subclass of _AbstractRoleAuthorizationStrategy_ needs an implementation of 
interface _IRoleCheckingStrategy_ to be instantiated. For this purpose in the 
code above we used the application class itself because its base class 
_AuthenticatedWebApplication_ already implements interface 
_IRoleCheckingStrategy_. By default _AuthenticatedWebApplication_ checks for 
authorizations using the roles returned by the current 
_AbstractAuthenticatedWebSession_. As final step inside init we grant the [...]
 
 The code from session class has three interesting methods. The first is 
_authenticate()_ which considers as valid credentials every pair of username 
and password having the same value. The second notable method is _getRoles()_ 
which returns role SIGNED_IN if user is authenticated and it adds role ADMIN if 
username is equal to superuser. Finally, we have method _signOut()_ which has 
been overridden in order to clean the username field used internally to 
generate roles.
 
@@ -227,7 +227,16 @@ Just like custom “Page expired” page (see 
<<versioningCaching.adoc#_stateful
 
 ==== Using roles with annotations
 
-Strategy _AnnotationsRoleAuthorizationStrategy_ relies on two built-in 
annotations to handle role-based authorizations. These annotations are 
_AuthorizeInstantiation_ and _AuthorizeAction_. As their names suggest the 
first annotation specifies which roles are allowed to instantiate the annotated 
component while the second must be used to indicate which roles are allowed to 
perform a specific action on the annotated component.
+Strategy _AnnotationsRoleAuthorizationStrategy_ relies on a set of built-in 
annotations to handle role-based authorizations:
+
+|===
+|Annotation | Restricts
+|*_AuthorizeInstantiation_* |which roles are allowed to instantiate the 
annotated component
+|*_AuthorizeInstantiations_* |a group of _AuthorizeInstantiation_ rules that 
must all be satisfied
+|*_AuthorizeAction_* |which roles are allowed to perform a given action on the 
annotated component
+|*_AuthorizeActions_* |a group of _AuthorizeAction_ rules, one for each action
+|*_AuthorizeResource_* |which roles are allowed to request the annotated 
resource
+|===
 
 In the following example we use annotations to make a page accessible only to 
signed-in users and to enable it only if user has the ADMIN role:
 
@@ -252,6 +261,49 @@ public class AdminOnlyPage extends WebPage {
 }
 ----
 
+An annotation that lists no roles at all, like _@AuthorizeInstantiation()_, 
authorizes everybody. The next paragraph explains why that is useful.
+
+===== Annotations on packages
+
+Each of these annotations can be applied to an entire package instead of to a 
single class. A package is annotated in its _package-info.java_ file. Note the 
unusual order of the declarations: the annotations come before the package 
declaration, and the imports they need come after it.
+
+[source,java]
+----
+// only users with role ADMIN are allowed to create instances of pages in this 
package
+@AuthorizeInstantiation("ADMIN")
+package org.mycompany.admin;
+
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation;
+----
+
+The annotations on a package are a default that a class can override: *the 
annotations on a class replace the annotations on its package*, they are not 
added to them. Annotations at the same level are combined with AND, so every 
one of them has to grant access. For actions this is decided per action, which 
means that a class restricting only ENABLE still inherits the RENDER 
restriction of its package.
+
+This is what makes an annotation without roles useful: because 
_@AuthorizeInstantiation()_ replaces the rule of its package and requires no 
roles at all, it is the way to exempt a single class from the restrictions of 
its package.
+
+All of these annotations are _@Inherited_, and that has a consequence which is 
easy to overlook. The annotations *on a class* include the ones it inherits 
from a superclass, in whatever package that superclass happens to live. Such an 
inherited annotation counts as an annotation on the class, so it suppresses the 
annotation of the class' own package:
+
+[source,java]
+----
+//org/mycompany/base/SecuredPage.java
+@AuthorizeInstantiation("USER")
+public class SecuredPage extends WebPage {
+}
+
+//org/mycompany/admin/package-info.java
+@AuthorizeInstantiation("ADMIN")
+package org.mycompany.admin;
+
+//org/mycompany/admin/ReportPage.java
+public class ReportPage extends SecuredPage {
+}
+----
+
+WARNING: In the example above _ReportPage_ requires role USER and *not* role 
ADMIN. It inherits the annotation of _SecuredPage_, so the ADMIN restriction of 
its own package is never applied. This means that moving a page into a 
restricted package has no effect at all when one of its superclasses is 
annotated. Annotate such a subclass explicitly to give it the roles of its 
package.
+
+NOTE: A package annotation applies to that one package and to nothing else. 
Java has no annotation inheritance between packages, so annotating 
_org.mycompany_ does not restrict the classes in _org.mycompany.admin_.
+
+NOTE: The _package-info.java_ file has to be compiled and shipped with the 
application, and it has to be loaded by the same class loader as the classes of 
its package, or its annotations cannot be found at runtime.
+
 === Catching an unauthorized component instantiation
 
 Interface _IUnauthorizedComponentInstantiationListener_ (in package 
_org.apache.wicket.authorization_) is provided to give the chance to handle the 
case in which a user tries to instantiate a component without having the 
permissions to do it. The method defined inside this interface is 
_onUnauthorizedInstantiation(Component)_ and it is executed whenever a user 
attempts to execute an unauthorized instantiation.

Reply via email to