WICKET-7200 align class-vs-package resolution in the role annotations - #1562
Open
papegaaij wants to merge 1 commit into
Open
WICKET-7200 align class-vs-package resolution in the role annotations#1562papegaaij wants to merge 1 commit into
papegaaij wants to merge 1 commit into
Conversation
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) <noreply@anthropic.com>
papegaaij
marked this pull request as ready for review
August 31, 2026 09:04
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
WICKET-7200
The problem
AnnotationsRoleAuthorizationStrategyimplemented three different rules for combining a class level annotation with a package level one, and none of them were documented:@Target@AuthorizeInstantiationTYPE,PACKAGE@AuthorizeInstantiationsTYPE@AuthorizeActionTYPE,PACKAGE@AuthorizeActionsTYPE@AuthorizeResourceTYPE,PACKAGETwo consequences that users hit:
@Inheritedand the class lookup runs first, an annotation on a superclass in any package counts as the class level annotation and therefore suppresses the package level annotation of the subclass' own package:Moving a page into a package guarded by
package-info.javahas no effect when one of its superclasses is annotated.@AuthorizeActionhas declaredElementType.PACKAGEsince February 2006, but the action path never looked at the package. A@AuthorizeActionin apackage-info.javacompiles, looks correct, and protects nothing.Why override, and not AND
Override is the rule that was designed, and it was affirmed when someone asked for the opposite. The pre-2010 code carried the comment "If roles are defined for the class, that overrides the package", and WICKET-3240 ("…package==false, class==true returns true") asked for conjunctive behaviour and was resolved without changing behaviour — only short-circuiting the lookup, with the comment that is still there: "Check class annotation first because it is more specific than package annotation".
The conjunction on resources, by contrast, was never a design decision. WICKET-5749 introduced it in 7.0.0 as
class || packagewith a missing annotation counting as deny. The follow-up commit "non-annotated resources should be allowed, not denied" corrected the missing-annotation case to allow, and sincetrue || anythingis always true, that forced||to become&&in the same edit purely to keep the annotation functional. That commit message says nothing about class-vs-package composition, and the annotation's javadoc still claimed it "works analogously to AuthorizeInstantiation" — which it did not.What this PR does
One resolution path, shared by all three checks: gather the rules declared on the class, and fall back to the package only when the class says nothing.
@AuthorizeAction/@AuthorizeActionshonour package level annotations, resolved per action name: a class level rule forENABLEoverrides only the package rule forENABLE, and the package rule forRENDERstill applies. Resolving wholesale would mean that annotating a class for one action silently unprotects it for the other.@AuthorizeActionsgainsElementType.PACKAGE, without which a package cannot express more than one action rule.@AuthorizeResourcereplaces the annotation of its package instead of being AND-ed with it. This also removes an unguardedresourceClass.getPackage()dereference, which the instantiation path already guarded.@AuthorizeInstantiationsparticipates in the override like every other rule, and gainsElementType.PACKAGE.Documentation, which is what prompted the ticket:
AnnotationsRoleAuthorizationStrategycarries the canonical rules, including the@Inheritedpitfall, that package annotations do not cascade to subpackages, thatpackage-info.javahas to be compiled and shipped, and that an annotation without roles authorizes everybody and is therefore the way to exempt a class from its package.@AuthorizeActionclaimed it "must be embedded in the AuthorizeActions annotation" (it works standalone, aswicket-examplesand the tests show),@AuthorizeActions' example showed the singular annotation,@AuthorizeInstantiationshad a self-referential@see, and bothroles()/deny()documented their default as "an empty string" rather than an empty array.package-info.javaform, the resolution rule, the worked@Inheritedtrap and the caveats.Behaviour changes
Both are loosening, and both affect only applications that annotate a class and its package:
@AuthorizeInstantiationsinside a package carrying@AuthorizeInstantiationpreviously required both, and now requires only the class ruleset.Package level
@AuthorizeActionchanges from no-op to enforced, which can only tighten authorization, and only for applications that already wrote an annotation that never worked.The public and protected API surface of
AnnotationsRoleAuthorizationStrategyis unchanged, and the@Targetadditions are additive, so no dependent module is affected at compile time.Testing
mvn -pl wicket-auth-roles test→ 52 tests pass. The 41 pre-existing tests are untouched and unchanged.New
AnnotationsRolePackageTestadds 11 tests over real fixture classes in real annotated packages. Mocks cannot be used here, because a generated mock does not live in the package of the class it mocks — which is also why the package level behaviour had never been covered. Nothing in the repository exercised a package level annotation before this PR.Reverting only the strategy to its previous version fails exactly 4 of the 11 new tests — the resource override, the
@AuthorizeInstantiationsoverride, package level actions, and per-action resolution. The other 7 pass either way, so they pin pre-existing behaviour, including the@Inheritedtrap.mvn -pl wicket-auth-roles javadoc:javadocis clean, andmvn -pl wicket-user-guide package -P guiderenders the new section with no new possible invalid reference.🤖 Generated with Claude Code