You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@wicket.apache.org by John Sarman <jo...@gmail.com> on 2014/02/26 18:53:24 UTC

AutoMounter Annotation Processor

Devs,
   Recently, I wanted to integrate wicket-auth-roles with servlet 3.0
container security.  What I ultimately did was create a simple SigninPage
as so:

public class LoginInterceptorPage extends WebPage {

   public  LoginInterceptorPage() {
         continueToOriginalDestination();
   }

}

In my Application class that extends AuthenticatedWebApplication I
implement the getSignInPageClass() to return the LoginInterceptorPage.

The trick is to mount the LoginInterceptorPage to a url that the
servletContainer will pick up  as a security-contraint.
web.xml snippet
<security-constraint>
        <display-name>Constraint1</display-name>
        <web-resource-collection>
            <web-resource-name>secure</web-resource-name>
            <description/>
            <url-pattern>/sec/*</url-pattern>       <!-------------   If
this pattern is seen then redirect to login scheme if not logged in  -->
        </web-resource-collection>
        <auth-constraint>
            <description>user</description>
            <role-name>USER</role-name>
        </auth-constraint>
    </security-constraint>

and in Application.init()  I added
mountPage("sec/login.html",LoginInterceptorPage.class); // maps to a url
matching pattern set in web.xml

Also I added getSecuritySettings().setAuthorizationStrategy(new
AnnotationsRoleAuthorizationStrategy(this));
to use the annotation @AuthorizeInstantiation to mark pages or packages as
needing authorization.

For the WebSession I implement as so:
public class ServletContainerAuthenticatedWebSession extends
AbstractAuthenticatedWebSession {

    private static final long serialVersionUID = 1L;

    /**
     * @return Current authenticated web session
     */
    public static ServletContainerAuthenticatedWebSession get() {
        return (ServletContainerAuthenticatedWebSession) Session.get();
    }

    public ServletContainerAuthenticatedWebSession(Request request) {
        super(request);
    }

    @Override
    public Roles getRoles() {
        if (isSignedIn()) {
            return new UserPrincipalRoles();
        }
        return null;
    }

    @Override
    public boolean isSignedIn() {
        return getRequest().getUserPrincipal() != null;
    }

    public void signOut() {
        if (isSignedIn()) {
            try {
                getRequest().logout();
            } catch (ServletException ex) {
                throw new RuntimeException(ex);
            }
        }
    }

    private static HttpServletRequest getRequest() {
        return (HttpServletRequest)
RequestCycle.get().getRequest().getContainerRequest();
    }


}

Where UserPrincipalRoles is :
public class UserPrincipalRoles extends Roles{

    public UserPrincipalRoles() {

    }

    @Override
    public boolean hasAllRoles(Roles roles) {
        Iterator<String> allRoles = roles.iterator();
        boolean result = true;
        while(allRoles.hasNext() && result) {
            result = getRequest().isUserInRole(allRoles.next());
        }
        return result;
    }

    @Override
    public boolean hasAnyRole(Roles roles) {
       Iterator<String> allRoles = roles.iterator();
        boolean result = false;
        while(allRoles.hasNext() && !result) {
            result = getRequest().isUserInRole(allRoles.next());
        }
        return result;
    }

    @Override
    public boolean hasRole(String role) {
        return getRequest().isUserInRole(role);
    }

    private static HttpServletRequest getRequest() {
     return
(HttpServletRequest)RequestCycle.get().getRequest().getContainerRequest();
    }


With this I solely rely on the container to determine if the session is
logged in via  getRequest().getUserPrincipal() != null; and role matching
using the container.

What happens is when a page that is annotated with  @AuthorizedInstantion
and the user is not authenticated, the
AuthenticatedWebApplication.onUnauthorizedInstantiation is triggered which
then redirects to the LoginInterceptorPage.  Since the LoginInterceptorPage
is mounted as sec/Login.html and the security-constraint is looks for
anything in sec/*  this forces the  auth-method of the login-config in the
web.xml to occur.

<login-config>
        <auth-method>FORM</auth-method>
        <realm-name>file-realm</realm-name>
        <form-login-config>
            <form-login-page>/login.html</form-login-page>
            <form-error-page>/error.html</form-error-page>
        </form-login-config>
    </login-config>

In my case I setup the auth-method to be FORM and added a login.html to the
root of the war file.  The login.html:
<form action="j_security_check" method=post>
    <p><strong>Please Enter Your User Name: </strong>
    <input type="text" name="j_username" size="25">
    <p><p><strong>Please Enter Your Password: </strong>
    <input type="password" size="15" name="j_password">
    <p><p>
    <input type="submit" value="Submit">
    <input type="reset" value="Reset">
</form>

I set up the file-realm for testing, but obviously ldap, database, SPENAGO,
etc can replace this for production. Also html can be jazzed up :)

Once the user successfully authenticated, the servlet container redirects
back to sec/login.html which then calls continueToOriginalDestination() and
ultimately to the page annotated with  @AuthorizedInstantion, assuming the
user had the proper role the page would be displayed.


This works great.  So from there what I realized is I should mount all
pages that are annotated with  @AuthorizedInstantion to a url that is
caught in the security-constraint of web.xml.  This I call double checking,
that is first the security container automatically intercepts any call to a
bookmarkable page ie setResponse(SomeSecurePage.class)  and forces the
login page, or if a non bookmarkable call occurs the
onUnauthorizedInstantiation is triggered which redirects to the
LoginInterceptorPage which is mounted to a url that is mapped in the
security-contraint in the web.xml. Furthurmore if a bookmarkable secure
page is not mapped to a url matching the security-constraint the
 onUnauthorizedInstantiation is triggered which intercepts and forces the
login page of the container.

Nice, with no changes to wicket I can easily integrate wicket-auth-role to
use the servlet 3.0 security. (pre 3.0 the getUserPrincipal and such was
not in HttpServletRequest)  Also one could create a login page with wicket
and call request.authenticate(user,pass), but login-config did not like it
when I set the url to that page.


All that being said what I needed was a way to AutoMount any page or
package that had the  @AuthorizedInstantion annotation.  Based on that I
created an AnnotationProcessor that generated source which was a list of
url and classes.  Here is a sample of the generated source

public class MyAppMountInfo implements MountInfo
{
@Override
public List<Mount> getMountPoints() {
List<Mount> ret = new ArrayList<Mount>();
ret.add(new
Mount("sec/custom/Page3.shtml", com.example.ui.user2.Page3.class));
ret.add(new Mount("sec/yo/Yo.html", com.example.ui.user2.Page4.class));
ret.add(new Mount("sec/user.html", com.example.ui.user.Page2.class));
ret.add(new
Mount("sec/AdminPage.shtml", com.example.ui.admin.AdminPage.class));
return ret;
}
}



The class is generated as AppName + MountInfo and implements MountInfo so
that in the app code you can do something like so
public class AutoMounter {

    public static boolean mountAll(WebApplication app) {
        String mapInfoClassName = app.getClass().getCanonicalName() +
"MountInfo";
        try {

            MountInfo mountInfo = (MountInfo)
Class.forName(mapInfoClassName).newInstance();

            for (MountInfo.Mount mp : mountInfo.getMountPoints()) {
                app.mountPage(mp.path, (Class<Page>) mp.pageClass);
            }
            return true;
        } catch (Exception ex) {
            return false;
        }
    }
}

where MountInfo is:
public interface MountInfo {

    List<Mount> getMountPoints();

    public static class Mount {

        String path;
        Class<? extends Page> pageClass;

        public Mount(String path, Class<? extends Page> pageClass) {
            this.path = path;
            this.pageClass = pageClass;
        }

    }
}


In MyApp,init()  just call AutoMounter.mountAll(this);

For the processor to actually generate the code you also nee to add the
@AutoMount(secure=true)  to the AppClass, this is the annotation that the
processor looks for to process the code.

public @interface AutoMount {

    String defaultRoot() default "";
    String mimeExtension() default "";
    boolean secure() default false;
    String secureRoot() default "secure";
}

When implementing this I also realized that maybe users just wanted to
AutoMount pages even without doing the secureMount stuff, so I made it also
possible to Mount any page.  To set a Mount Point you add the @MountPoint
to a page or package-info if you would like to automount all pages in a
package.

examples:

@MountPoint(path="users/ImportantPage.html")
public class ImportantUserPage extends WebPage {...

ultimately creates  mountPage("users/ImportantPage.html",
com.example.ui.user.ImportantUserPage.class);

However assume AutoMount(defaultRoot="users", mimeExtension="php")  is set
on the App Class and

@MountPoint
public class ImportantUserPage extends WebPage {...

creates  mountPage("users/ImportantUserPage.php",
com.example.ui.user.ImportantUserPage.class);  // Really php it is just to
prove a point


Finally the reason I initially created this code
 AutoMount(secure=true, secureRoot="sec")
public class MyApp ....



@AuthorizedInstantiation({"USERS","ADMIN"})
public class SecureUserPage extends WebPage {


creates mountPage("sec/SecureUserPage",
com.example.ui.user.secure.SecureUserPage.class);


I already created code and wonder if I should create a pull request, or
make it a wicketstuff or just use it for myself.   I personally think it
would be nice in wicket itself but I wanted to ask first before going any
further.


Thanks,
John Sarman

Re: AutoMounter Annotation Processor

Posted by John Sarman <jo...@gmail.com>.
Tom,
Thanks for the link.  That is basically a Runtime equivalent of what I
produced.  The code relies on spring-framework to actually parse through
the classes at runtime, which one of my goals was to not depend on any jars
other than wicket jars and the Java core so I may just create a separate
wicketstuff.

Thanks again for sharing that project with me.

John


On Tue, Mar 4, 2014 at 1:28 PM, Burton, Tom F (DOR)
<to...@alaska.gov>wrote:

> Hello John,
>
> Not to further muddy the water or insult the good work that's been done.
> https://github.com/wicketstuff/core/wiki/Annotation
>
> I don't know how widely this project is used, but it sounds like you've
> duplicated and possibly extended the project.  Maybe they could be merged?
>
>
> Tom Burton
>
> -----Original Message-----
> From: Martin Grigorov [mailto:mgrigorov@apache.org]
> Sent: Tuesday, March 04, 2014 1:16 AM
> To: dev@wicket.apache.org
> Subject: Re: AutoMounter Annotation Processor
>
> Hi John,
>
>
> On Wed, Feb 26, 2014 at 7:53 PM, John Sarman <jo...@gmail.com> wrote:
>
> > Devs,
> >    Recently, I wanted to integrate wicket-auth-roles with servlet 3.0
> > container security.  What I ultimately did was create a simple
> > SigninPage as so:
> >
> > public class LoginInterceptorPage extends WebPage {
> >
> >    public  LoginInterceptorPage() {
> >          continueToOriginalDestination();
> >    }
> >
> > }
> >
> > In my Application class that extends AuthenticatedWebApplication I
> > implement the getSignInPageClass() to return the LoginInterceptorPage.
> >
> > The trick is to mount the LoginInterceptorPage to a url that the
> > servletContainer will pick up  as a security-contraint.
> > web.xml snippet
> > <security-constraint>
> >         <display-name>Constraint1</display-name>
> >         <web-resource-collection>
> >             <web-resource-name>secure</web-resource-name>
> >             <description/>
> >             <url-pattern>/sec/*</url-pattern>       <!-------------   If
> > this pattern is seen then redirect to login scheme if not logged in  -->
> >         </web-resource-collection>
> >         <auth-constraint>
> >             <description>user</description>
> >             <role-name>USER</role-name>
> >         </auth-constraint>
> >     </security-constraint>
> >
> > and in Application.init()  I added
> > mountPage("sec/login.html",LoginInterceptorPage.class); // maps to a
> > url matching pattern set in web.xml
> >
> > Also I added getSecuritySettings().setAuthorizationStrategy(new
> > AnnotationsRoleAuthorizationStrategy(this));
> > to use the annotation @AuthorizeInstantiation to mark pages or
> > packages as needing authorization.
> >
> > For the WebSession I implement as so:
> > public class ServletContainerAuthenticatedWebSession extends
> > AbstractAuthenticatedWebSession {
> >
> >     private static final long serialVersionUID = 1L;
> >
> >     /**
> >      * @return Current authenticated web session
> >      */
> >     public static ServletContainerAuthenticatedWebSession get() {
> >         return (ServletContainerAuthenticatedWebSession) Session.get();
> >     }
> >
> >     public ServletContainerAuthenticatedWebSession(Request request) {
> >         super(request);
> >     }
> >
> >     @Override
> >     public Roles getRoles() {
> >         if (isSignedIn()) {
> >             return new UserPrincipalRoles();
> >         }
> >         return null;
> >     }
> >
> >     @Override
> >     public boolean isSignedIn() {
> >         return getRequest().getUserPrincipal() != null;
> >     }
> >
> >     public void signOut() {
> >         if (isSignedIn()) {
> >             try {
> >                 getRequest().logout();
> >             } catch (ServletException ex) {
> >                 throw new RuntimeException(ex);
> >             }
> >         }
> >     }
> >
> >     private static HttpServletRequest getRequest() {
> >         return (HttpServletRequest)
> > RequestCycle.get().getRequest().getContainerRequest();
> >     }
> >
> >
> > }
> >
> > Where UserPrincipalRoles is :
> > public class UserPrincipalRoles extends Roles{
> >
> >     public UserPrincipalRoles() {
> >
> >     }
> >
> >     @Override
> >     public boolean hasAllRoles(Roles roles) {
> >         Iterator<String> allRoles = roles.iterator();
> >         boolean result = true;
> >         while(allRoles.hasNext() && result) {
> >             result = getRequest().isUserInRole(allRoles.next());
> >         }
> >         return result;
> >     }
> >
> >     @Override
> >     public boolean hasAnyRole(Roles roles) {
> >        Iterator<String> allRoles = roles.iterator();
> >         boolean result = false;
> >         while(allRoles.hasNext() && !result) {
> >             result = getRequest().isUserInRole(allRoles.next());
> >         }
> >         return result;
> >     }
> >
> >     @Override
> >     public boolean hasRole(String role) {
> >         return getRequest().isUserInRole(role);
> >     }
> >
> >     private static HttpServletRequest getRequest() {
> >      return
> >
> (HttpServletRequest)RequestCycle.get().getRequest().getContainerRequest();
> >     }
> >
> >
> > With this I solely rely on the container to determine if the session
> > is logged in via  getRequest().getUserPrincipal() != null; and role
> > matching using the container.
> >
> > What happens is when a page that is annotated with
> > @AuthorizedInstantion and the user is not authenticated, the
> > AuthenticatedWebApplication.onUnauthorizedInstantiation is triggered
> > which then redirects to the LoginInterceptorPage.  Since the
> > LoginInterceptorPage is mounted as sec/Login.html and the
> > security-constraint is looks for anything in sec/*  this forces the
> > auth-method of the login-config in the web.xml to occur.
> >
> > <login-config>
> >         <auth-method>FORM</auth-method>
> >         <realm-name>file-realm</realm-name>
> >         <form-login-config>
> >             <form-login-page>/login.html</form-login-page>
> >             <form-error-page>/error.html</form-error-page>
> >         </form-login-config>
> >     </login-config>
> >
> > In my case I setup the auth-method to be FORM and added a login.html
> > to the root of the war file.  The login.html:
> > <form action="j_security_check" method=post>
> >     <p><strong>Please Enter Your User Name: </strong>
> >     <input type="text" name="j_username" size="25">
> >     <p><p><strong>Please Enter Your Password: </strong>
> >     <input type="password" size="15" name="j_password">
> >     <p><p>
> >     <input type="submit" value="Submit">
> >     <input type="reset" value="Reset"> </form>
> >
> > I set up the file-realm for testing, but obviously ldap, database,
> > SPENAGO, etc can replace this for production. Also html can be jazzed
> > up :)
> >
> > Once the user successfully authenticated, the servlet container
> > redirects back to sec/login.html which then calls
> > continueToOriginalDestination() and ultimately to the page annotated
> > with  @AuthorizedInstantion, assuming the user had the proper role the
> page would be displayed.
> >
> >
> > This works great.  So from there what I realized is I should mount all
> > pages that are annotated with  @AuthorizedInstantion to a url that is
> > caught in the security-constraint of web.xml.  This I call double
> > checking, that is first the security container automatically
> > intercepts any call to a bookmarkable page ie
> > setResponse(SomeSecurePage.class)  and forces the login page, or if a
> > non bookmarkable call occurs the onUnauthorizedInstantiation is
> > triggered which redirects to the LoginInterceptorPage which is mounted
> > to a url that is mapped in the security-contraint in the web.xml.
> > Furthurmore if a bookmarkable secure page is not mapped to a url
> > matching the security-constraint the  onUnauthorizedInstantiation is
> > triggered which intercepts and forces the login page of the container.
> >
> > Nice, with no changes to wicket I can easily integrate
> > wicket-auth-role to use the servlet 3.0 security. (pre 3.0 the
> > getUserPrincipal and such was not in HttpServletRequest)  Also one
> > could create a login page with wicket and call
> > request.authenticate(user,pass), but login-config did not like it when I
> set the url to that page.
> >
> >
> > All that being said what I needed was a way to AutoMount any page or
> > package that had the  @AuthorizedInstantion annotation.  Based on that
> > I created an AnnotationProcessor that generated source which was a
> > list of url and classes.  Here is a sample of the generated source
> >
> > public class MyAppMountInfo implements MountInfo { @Override public
> > List<Mount> getMountPoints() { List<Mount> ret = new
> > ArrayList<Mount>(); ret.add(new Mount("sec/custom/Page3.shtml",
> > com.example.ui.user2.Page3.class));
> > ret.add(new Mount("sec/yo/Yo.html",
> > com.example.ui.user2.Page4.class));
> > ret.add(new Mount("sec/user.html", com.example.ui.user.Page2.class));
> > ret.add(new
> > Mount("sec/AdminPage.shtml", com.example.ui.admin.AdminPage.class));
> > return ret;
> > }
> > }
> >
> >
> >
> > The class is generated as AppName + MountInfo and implements MountInfo
> > so that in the app code you can do something like so public class
> > AutoMounter {
> >
> >     public static boolean mountAll(WebApplication app) {
> >         String mapInfoClassName = app.getClass().getCanonicalName() +
> > "MountInfo";
> >         try {
> >
> >             MountInfo mountInfo = (MountInfo)
> > Class.forName(mapInfoClassName).newInstance();
> >
> >             for (MountInfo.Mount mp : mountInfo.getMountPoints()) {
> >                 app.mountPage(mp.path, (Class<Page>) mp.pageClass);
> >             }
> >             return true;
> >         } catch (Exception ex) {
> >             return false;
> >         }
> >     }
> > }
> >
> > where MountInfo is:
> > public interface MountInfo {
> >
> >     List<Mount> getMountPoints();
> >
> >     public static class Mount {
> >
> >         String path;
> >         Class<? extends Page> pageClass;
> >
> >         public Mount(String path, Class<? extends Page> pageClass) {
> >             this.path = path;
> >             this.pageClass = pageClass;
> >         }
> >
> >     }
> > }
> >
> >
> > In MyApp,init()  just call AutoMounter.mountAll(this);
> >
> > For the processor to actually generate the code you also nee to add
> > the
> > @AutoMount(secure=true)  to the AppClass, this is the annotation that
> > the processor looks for to process the code.
> >
> > public @interface AutoMount {
> >
> >     String defaultRoot() default "";
> >     String mimeExtension() default "";
> >     boolean secure() default false;
> >     String secureRoot() default "secure"; }
> >
> > When implementing this I also realized that maybe users just wanted to
> > AutoMount pages even without doing the secureMount stuff, so I made it
> > also possible to Mount any page.  To set a Mount Point you add the
> > @MountPoint to a page or package-info if you would like to automount
> > all pages in a package.
> >
> > examples:
> >
> > @MountPoint(path="users/ImportantPage.html")
> > public class ImportantUserPage extends WebPage {...
> >
> > ultimately creates  mountPage("users/ImportantPage.html",
> > com.example.ui.user.ImportantUserPage.class);
> >
> > However assume AutoMount(defaultRoot="users", mimeExtension="php")  is
> > set on the App Class and
> >
> > @MountPoint
> > public class ImportantUserPage extends WebPage {...
> >
> > creates  mountPage("users/ImportantUserPage.php",
> > com.example.ui.user.ImportantUserPage.class);  // Really php it is
> > just to prove a point
> >
> >
> > Finally the reason I initially created this code
> > AutoMount(secure=true, secureRoot="sec") public class MyApp ....
> >
> >
> >
> > @AuthorizedInstantiation({"USERS","ADMIN"})
> > public class SecureUserPage extends WebPage {
> >
> >
> > creates mountPage("sec/SecureUserPage",
> > com.example.ui.user.secure.SecureUserPage.class);
> >
> >
> > I already created code and wonder if I should create a pull request, or
> > make it a wicketstuff or just use it for myself.   I personally think it
> > would be nice in wicket itself but I wanted to ask first before going
> > any further.
> >
>
> I think there is not enough interest in this functionality at the moment.
> There were no requests from Wicket users for supporting this neither in
> Jira nor in the mailing lists.
> I believe this is so because Wicket's authentication/authorization is much
> simpler than the ones in Servlet specification.
>
> Additionally your code will require a new compilation step - the
> annotation processing. So far Wicket has tried to avoid any code generation.
>
> Anyway, I'd love to redirect any potential user to the place where you
> decide to share your implementation - WicketStuff or your own repository.
> If there is more demand later then we can reconsider your offer to donate
> this code to Wicket itself.
>
> Thank you!
>
>
> >
> >
> > Thanks,
> > John Sarman
> >
>

RE: AutoMounter Annotation Processor

Posted by "Burton, Tom F (DOR)" <to...@alaska.gov>.
Hello John,

Not to further muddy the water or insult the good work that’s been done.
https://github.com/wicketstuff/core/wiki/Annotation 

I don't know how widely this project is used, but it sounds like you've duplicated and possibly extended the project.  Maybe they could be merged?


Tom Burton

-----Original Message-----
From: Martin Grigorov [mailto:mgrigorov@apache.org] 
Sent: Tuesday, March 04, 2014 1:16 AM
To: dev@wicket.apache.org
Subject: Re: AutoMounter Annotation Processor

Hi John,


On Wed, Feb 26, 2014 at 7:53 PM, John Sarman <jo...@gmail.com> wrote:

> Devs,
>    Recently, I wanted to integrate wicket-auth-roles with servlet 3.0 
> container security.  What I ultimately did was create a simple 
> SigninPage as so:
>
> public class LoginInterceptorPage extends WebPage {
>
>    public  LoginInterceptorPage() {
>          continueToOriginalDestination();
>    }
>
> }
>
> In my Application class that extends AuthenticatedWebApplication I 
> implement the getSignInPageClass() to return the LoginInterceptorPage.
>
> The trick is to mount the LoginInterceptorPage to a url that the 
> servletContainer will pick up  as a security-contraint.
> web.xml snippet
> <security-constraint>
>         <display-name>Constraint1</display-name>
>         <web-resource-collection>
>             <web-resource-name>secure</web-resource-name>
>             <description/>
>             <url-pattern>/sec/*</url-pattern>       <!-------------   If
> this pattern is seen then redirect to login scheme if not logged in  -->
>         </web-resource-collection>
>         <auth-constraint>
>             <description>user</description>
>             <role-name>USER</role-name>
>         </auth-constraint>
>     </security-constraint>
>
> and in Application.init()  I added
> mountPage("sec/login.html",LoginInterceptorPage.class); // maps to a 
> url matching pattern set in web.xml
>
> Also I added getSecuritySettings().setAuthorizationStrategy(new
> AnnotationsRoleAuthorizationStrategy(this));
> to use the annotation @AuthorizeInstantiation to mark pages or 
> packages as needing authorization.
>
> For the WebSession I implement as so:
> public class ServletContainerAuthenticatedWebSession extends 
> AbstractAuthenticatedWebSession {
>
>     private static final long serialVersionUID = 1L;
>
>     /**
>      * @return Current authenticated web session
>      */
>     public static ServletContainerAuthenticatedWebSession get() {
>         return (ServletContainerAuthenticatedWebSession) Session.get();
>     }
>
>     public ServletContainerAuthenticatedWebSession(Request request) {
>         super(request);
>     }
>
>     @Override
>     public Roles getRoles() {
>         if (isSignedIn()) {
>             return new UserPrincipalRoles();
>         }
>         return null;
>     }
>
>     @Override
>     public boolean isSignedIn() {
>         return getRequest().getUserPrincipal() != null;
>     }
>
>     public void signOut() {
>         if (isSignedIn()) {
>             try {
>                 getRequest().logout();
>             } catch (ServletException ex) {
>                 throw new RuntimeException(ex);
>             }
>         }
>     }
>
>     private static HttpServletRequest getRequest() {
>         return (HttpServletRequest)
> RequestCycle.get().getRequest().getContainerRequest();
>     }
>
>
> }
>
> Where UserPrincipalRoles is :
> public class UserPrincipalRoles extends Roles{
>
>     public UserPrincipalRoles() {
>
>     }
>
>     @Override
>     public boolean hasAllRoles(Roles roles) {
>         Iterator<String> allRoles = roles.iterator();
>         boolean result = true;
>         while(allRoles.hasNext() && result) {
>             result = getRequest().isUserInRole(allRoles.next());
>         }
>         return result;
>     }
>
>     @Override
>     public boolean hasAnyRole(Roles roles) {
>        Iterator<String> allRoles = roles.iterator();
>         boolean result = false;
>         while(allRoles.hasNext() && !result) {
>             result = getRequest().isUserInRole(allRoles.next());
>         }
>         return result;
>     }
>
>     @Override
>     public boolean hasRole(String role) {
>         return getRequest().isUserInRole(role);
>     }
>
>     private static HttpServletRequest getRequest() {
>      return
> (HttpServletRequest)RequestCycle.get().getRequest().getContainerRequest();
>     }
>
>
> With this I solely rely on the container to determine if the session 
> is logged in via  getRequest().getUserPrincipal() != null; and role 
> matching using the container.
>
> What happens is when a page that is annotated with  
> @AuthorizedInstantion and the user is not authenticated, the 
> AuthenticatedWebApplication.onUnauthorizedInstantiation is triggered 
> which then redirects to the LoginInterceptorPage.  Since the 
> LoginInterceptorPage is mounted as sec/Login.html and the 
> security-constraint is looks for anything in sec/*  this forces the  
> auth-method of the login-config in the web.xml to occur.
>
> <login-config>
>         <auth-method>FORM</auth-method>
>         <realm-name>file-realm</realm-name>
>         <form-login-config>
>             <form-login-page>/login.html</form-login-page>
>             <form-error-page>/error.html</form-error-page>
>         </form-login-config>
>     </login-config>
>
> In my case I setup the auth-method to be FORM and added a login.html 
> to the root of the war file.  The login.html:
> <form action="j_security_check" method=post>
>     <p><strong>Please Enter Your User Name: </strong>
>     <input type="text" name="j_username" size="25">
>     <p><p><strong>Please Enter Your Password: </strong>
>     <input type="password" size="15" name="j_password">
>     <p><p>
>     <input type="submit" value="Submit">
>     <input type="reset" value="Reset"> </form>
>
> I set up the file-realm for testing, but obviously ldap, database, 
> SPENAGO, etc can replace this for production. Also html can be jazzed 
> up :)
>
> Once the user successfully authenticated, the servlet container 
> redirects back to sec/login.html which then calls 
> continueToOriginalDestination() and ultimately to the page annotated 
> with  @AuthorizedInstantion, assuming the user had the proper role the page would be displayed.
>
>
> This works great.  So from there what I realized is I should mount all 
> pages that are annotated with  @AuthorizedInstantion to a url that is 
> caught in the security-constraint of web.xml.  This I call double 
> checking, that is first the security container automatically 
> intercepts any call to a bookmarkable page ie 
> setResponse(SomeSecurePage.class)  and forces the login page, or if a 
> non bookmarkable call occurs the onUnauthorizedInstantiation is 
> triggered which redirects to the LoginInterceptorPage which is mounted 
> to a url that is mapped in the security-contraint in the web.xml. 
> Furthurmore if a bookmarkable secure page is not mapped to a url 
> matching the security-constraint the  onUnauthorizedInstantiation is 
> triggered which intercepts and forces the login page of the container.
>
> Nice, with no changes to wicket I can easily integrate 
> wicket-auth-role to use the servlet 3.0 security. (pre 3.0 the 
> getUserPrincipal and such was not in HttpServletRequest)  Also one 
> could create a login page with wicket and call 
> request.authenticate(user,pass), but login-config did not like it when I set the url to that page.
>
>
> All that being said what I needed was a way to AutoMount any page or 
> package that had the  @AuthorizedInstantion annotation.  Based on that 
> I created an AnnotationProcessor that generated source which was a 
> list of url and classes.  Here is a sample of the generated source
>
> public class MyAppMountInfo implements MountInfo { @Override public 
> List<Mount> getMountPoints() { List<Mount> ret = new 
> ArrayList<Mount>(); ret.add(new Mount("sec/custom/Page3.shtml", 
> com.example.ui.user2.Page3.class));
> ret.add(new Mount("sec/yo/Yo.html", 
> com.example.ui.user2.Page4.class));
> ret.add(new Mount("sec/user.html", com.example.ui.user.Page2.class));
> ret.add(new
> Mount("sec/AdminPage.shtml", com.example.ui.admin.AdminPage.class));
> return ret;
> }
> }
>
>
>
> The class is generated as AppName + MountInfo and implements MountInfo 
> so that in the app code you can do something like so public class 
> AutoMounter {
>
>     public static boolean mountAll(WebApplication app) {
>         String mapInfoClassName = app.getClass().getCanonicalName() + 
> "MountInfo";
>         try {
>
>             MountInfo mountInfo = (MountInfo) 
> Class.forName(mapInfoClassName).newInstance();
>
>             for (MountInfo.Mount mp : mountInfo.getMountPoints()) {
>                 app.mountPage(mp.path, (Class<Page>) mp.pageClass);
>             }
>             return true;
>         } catch (Exception ex) {
>             return false;
>         }
>     }
> }
>
> where MountInfo is:
> public interface MountInfo {
>
>     List<Mount> getMountPoints();
>
>     public static class Mount {
>
>         String path;
>         Class<? extends Page> pageClass;
>
>         public Mount(String path, Class<? extends Page> pageClass) {
>             this.path = path;
>             this.pageClass = pageClass;
>         }
>
>     }
> }
>
>
> In MyApp,init()  just call AutoMounter.mountAll(this);
>
> For the processor to actually generate the code you also nee to add 
> the
> @AutoMount(secure=true)  to the AppClass, this is the annotation that 
> the processor looks for to process the code.
>
> public @interface AutoMount {
>
>     String defaultRoot() default "";
>     String mimeExtension() default "";
>     boolean secure() default false;
>     String secureRoot() default "secure"; }
>
> When implementing this I also realized that maybe users just wanted to 
> AutoMount pages even without doing the secureMount stuff, so I made it 
> also possible to Mount any page.  To set a Mount Point you add the 
> @MountPoint to a page or package-info if you would like to automount 
> all pages in a package.
>
> examples:
>
> @MountPoint(path="users/ImportantPage.html")
> public class ImportantUserPage extends WebPage {...
>
> ultimately creates  mountPage("users/ImportantPage.html",
> com.example.ui.user.ImportantUserPage.class);
>
> However assume AutoMount(defaultRoot="users", mimeExtension="php")  is 
> set on the App Class and
>
> @MountPoint
> public class ImportantUserPage extends WebPage {...
>
> creates  mountPage("users/ImportantUserPage.php",
> com.example.ui.user.ImportantUserPage.class);  // Really php it is 
> just to prove a point
>
>
> Finally the reason I initially created this code  
> AutoMount(secure=true, secureRoot="sec") public class MyApp ....
>
>
>
> @AuthorizedInstantiation({"USERS","ADMIN"})
> public class SecureUserPage extends WebPage {
>
>
> creates mountPage("sec/SecureUserPage", 
> com.example.ui.user.secure.SecureUserPage.class);
>
>
> I already created code and wonder if I should create a pull request, or
> make it a wicketstuff or just use it for myself.   I personally think it
> would be nice in wicket itself but I wanted to ask first before going 
> any further.
>

I think there is not enough interest in this functionality at the moment.
There were no requests from Wicket users for supporting this neither in Jira nor in the mailing lists.
I believe this is so because Wicket's authentication/authorization is much simpler than the ones in Servlet specification.

Additionally your code will require a new compilation step - the annotation processing. So far Wicket has tried to avoid any code generation.

Anyway, I'd love to redirect any potential user to the place where you decide to share your implementation - WicketStuff or your own repository.
If there is more demand later then we can reconsider your offer to donate this code to Wicket itself.

Thank you!


>
>
> Thanks,
> John Sarman
>

Re: AutoMounter Annotation Processor

Posted by Martin Grigorov <mg...@apache.org>.
Hi John,


On Wed, Feb 26, 2014 at 7:53 PM, John Sarman <jo...@gmail.com> wrote:

> Devs,
>    Recently, I wanted to integrate wicket-auth-roles with servlet 3.0
> container security.  What I ultimately did was create a simple SigninPage
> as so:
>
> public class LoginInterceptorPage extends WebPage {
>
>    public  LoginInterceptorPage() {
>          continueToOriginalDestination();
>    }
>
> }
>
> In my Application class that extends AuthenticatedWebApplication I
> implement the getSignInPageClass() to return the LoginInterceptorPage.
>
> The trick is to mount the LoginInterceptorPage to a url that the
> servletContainer will pick up  as a security-contraint.
> web.xml snippet
> <security-constraint>
>         <display-name>Constraint1</display-name>
>         <web-resource-collection>
>             <web-resource-name>secure</web-resource-name>
>             <description/>
>             <url-pattern>/sec/*</url-pattern>       <!-------------   If
> this pattern is seen then redirect to login scheme if not logged in  -->
>         </web-resource-collection>
>         <auth-constraint>
>             <description>user</description>
>             <role-name>USER</role-name>
>         </auth-constraint>
>     </security-constraint>
>
> and in Application.init()  I added
> mountPage("sec/login.html",LoginInterceptorPage.class); // maps to a url
> matching pattern set in web.xml
>
> Also I added getSecuritySettings().setAuthorizationStrategy(new
> AnnotationsRoleAuthorizationStrategy(this));
> to use the annotation @AuthorizeInstantiation to mark pages or packages as
> needing authorization.
>
> For the WebSession I implement as so:
> public class ServletContainerAuthenticatedWebSession extends
> AbstractAuthenticatedWebSession {
>
>     private static final long serialVersionUID = 1L;
>
>     /**
>      * @return Current authenticated web session
>      */
>     public static ServletContainerAuthenticatedWebSession get() {
>         return (ServletContainerAuthenticatedWebSession) Session.get();
>     }
>
>     public ServletContainerAuthenticatedWebSession(Request request) {
>         super(request);
>     }
>
>     @Override
>     public Roles getRoles() {
>         if (isSignedIn()) {
>             return new UserPrincipalRoles();
>         }
>         return null;
>     }
>
>     @Override
>     public boolean isSignedIn() {
>         return getRequest().getUserPrincipal() != null;
>     }
>
>     public void signOut() {
>         if (isSignedIn()) {
>             try {
>                 getRequest().logout();
>             } catch (ServletException ex) {
>                 throw new RuntimeException(ex);
>             }
>         }
>     }
>
>     private static HttpServletRequest getRequest() {
>         return (HttpServletRequest)
> RequestCycle.get().getRequest().getContainerRequest();
>     }
>
>
> }
>
> Where UserPrincipalRoles is :
> public class UserPrincipalRoles extends Roles{
>
>     public UserPrincipalRoles() {
>
>     }
>
>     @Override
>     public boolean hasAllRoles(Roles roles) {
>         Iterator<String> allRoles = roles.iterator();
>         boolean result = true;
>         while(allRoles.hasNext() && result) {
>             result = getRequest().isUserInRole(allRoles.next());
>         }
>         return result;
>     }
>
>     @Override
>     public boolean hasAnyRole(Roles roles) {
>        Iterator<String> allRoles = roles.iterator();
>         boolean result = false;
>         while(allRoles.hasNext() && !result) {
>             result = getRequest().isUserInRole(allRoles.next());
>         }
>         return result;
>     }
>
>     @Override
>     public boolean hasRole(String role) {
>         return getRequest().isUserInRole(role);
>     }
>
>     private static HttpServletRequest getRequest() {
>      return
> (HttpServletRequest)RequestCycle.get().getRequest().getContainerRequest();
>     }
>
>
> With this I solely rely on the container to determine if the session is
> logged in via  getRequest().getUserPrincipal() != null; and role matching
> using the container.
>
> What happens is when a page that is annotated with  @AuthorizedInstantion
> and the user is not authenticated, the
> AuthenticatedWebApplication.onUnauthorizedInstantiation is triggered which
> then redirects to the LoginInterceptorPage.  Since the LoginInterceptorPage
> is mounted as sec/Login.html and the security-constraint is looks for
> anything in sec/*  this forces the  auth-method of the login-config in the
> web.xml to occur.
>
> <login-config>
>         <auth-method>FORM</auth-method>
>         <realm-name>file-realm</realm-name>
>         <form-login-config>
>             <form-login-page>/login.html</form-login-page>
>             <form-error-page>/error.html</form-error-page>
>         </form-login-config>
>     </login-config>
>
> In my case I setup the auth-method to be FORM and added a login.html to the
> root of the war file.  The login.html:
> <form action="j_security_check" method=post>
>     <p><strong>Please Enter Your User Name: </strong>
>     <input type="text" name="j_username" size="25">
>     <p><p><strong>Please Enter Your Password: </strong>
>     <input type="password" size="15" name="j_password">
>     <p><p>
>     <input type="submit" value="Submit">
>     <input type="reset" value="Reset">
> </form>
>
> I set up the file-realm for testing, but obviously ldap, database, SPENAGO,
> etc can replace this for production. Also html can be jazzed up :)
>
> Once the user successfully authenticated, the servlet container redirects
> back to sec/login.html which then calls continueToOriginalDestination() and
> ultimately to the page annotated with  @AuthorizedInstantion, assuming the
> user had the proper role the page would be displayed.
>
>
> This works great.  So from there what I realized is I should mount all
> pages that are annotated with  @AuthorizedInstantion to a url that is
> caught in the security-constraint of web.xml.  This I call double checking,
> that is first the security container automatically intercepts any call to a
> bookmarkable page ie setResponse(SomeSecurePage.class)  and forces the
> login page, or if a non bookmarkable call occurs the
> onUnauthorizedInstantiation is triggered which redirects to the
> LoginInterceptorPage which is mounted to a url that is mapped in the
> security-contraint in the web.xml. Furthurmore if a bookmarkable secure
> page is not mapped to a url matching the security-constraint the
>  onUnauthorizedInstantiation is triggered which intercepts and forces the
> login page of the container.
>
> Nice, with no changes to wicket I can easily integrate wicket-auth-role to
> use the servlet 3.0 security. (pre 3.0 the getUserPrincipal and such was
> not in HttpServletRequest)  Also one could create a login page with wicket
> and call request.authenticate(user,pass), but login-config did not like it
> when I set the url to that page.
>
>
> All that being said what I needed was a way to AutoMount any page or
> package that had the  @AuthorizedInstantion annotation.  Based on that I
> created an AnnotationProcessor that generated source which was a list of
> url and classes.  Here is a sample of the generated source
>
> public class MyAppMountInfo implements MountInfo
> {
> @Override
> public List<Mount> getMountPoints() {
> List<Mount> ret = new ArrayList<Mount>();
> ret.add(new
> Mount("sec/custom/Page3.shtml", com.example.ui.user2.Page3.class));
> ret.add(new Mount("sec/yo/Yo.html", com.example.ui.user2.Page4.class));
> ret.add(new Mount("sec/user.html", com.example.ui.user.Page2.class));
> ret.add(new
> Mount("sec/AdminPage.shtml", com.example.ui.admin.AdminPage.class));
> return ret;
> }
> }
>
>
>
> The class is generated as AppName + MountInfo and implements MountInfo so
> that in the app code you can do something like so
> public class AutoMounter {
>
>     public static boolean mountAll(WebApplication app) {
>         String mapInfoClassName = app.getClass().getCanonicalName() +
> "MountInfo";
>         try {
>
>             MountInfo mountInfo = (MountInfo)
> Class.forName(mapInfoClassName).newInstance();
>
>             for (MountInfo.Mount mp : mountInfo.getMountPoints()) {
>                 app.mountPage(mp.path, (Class<Page>) mp.pageClass);
>             }
>             return true;
>         } catch (Exception ex) {
>             return false;
>         }
>     }
> }
>
> where MountInfo is:
> public interface MountInfo {
>
>     List<Mount> getMountPoints();
>
>     public static class Mount {
>
>         String path;
>         Class<? extends Page> pageClass;
>
>         public Mount(String path, Class<? extends Page> pageClass) {
>             this.path = path;
>             this.pageClass = pageClass;
>         }
>
>     }
> }
>
>
> In MyApp,init()  just call AutoMounter.mountAll(this);
>
> For the processor to actually generate the code you also nee to add the
> @AutoMount(secure=true)  to the AppClass, this is the annotation that the
> processor looks for to process the code.
>
> public @interface AutoMount {
>
>     String defaultRoot() default "";
>     String mimeExtension() default "";
>     boolean secure() default false;
>     String secureRoot() default "secure";
> }
>
> When implementing this I also realized that maybe users just wanted to
> AutoMount pages even without doing the secureMount stuff, so I made it also
> possible to Mount any page.  To set a Mount Point you add the @MountPoint
> to a page or package-info if you would like to automount all pages in a
> package.
>
> examples:
>
> @MountPoint(path="users/ImportantPage.html")
> public class ImportantUserPage extends WebPage {...
>
> ultimately creates  mountPage("users/ImportantPage.html",
> com.example.ui.user.ImportantUserPage.class);
>
> However assume AutoMount(defaultRoot="users", mimeExtension="php")  is set
> on the App Class and
>
> @MountPoint
> public class ImportantUserPage extends WebPage {...
>
> creates  mountPage("users/ImportantUserPage.php",
> com.example.ui.user.ImportantUserPage.class);  // Really php it is just to
> prove a point
>
>
> Finally the reason I initially created this code
>  AutoMount(secure=true, secureRoot="sec")
> public class MyApp ....
>
>
>
> @AuthorizedInstantiation({"USERS","ADMIN"})
> public class SecureUserPage extends WebPage {
>
>
> creates mountPage("sec/SecureUserPage",
> com.example.ui.user.secure.SecureUserPage.class);
>
>
> I already created code and wonder if I should create a pull request, or
> make it a wicketstuff or just use it for myself.   I personally think it
> would be nice in wicket itself but I wanted to ask first before going any
> further.
>

I think there is not enough interest in this functionality at the moment.
There were no requests from Wicket users for supporting this neither in
Jira nor in the mailing lists.
I believe this is so because Wicket's authentication/authorization is much
simpler than the ones in Servlet specification.

Additionally your code will require a new compilation step - the annotation
processing. So far Wicket has tried to avoid any code generation.

Anyway, I'd love to redirect any potential user to the place where you
decide to share your implementation - WicketStuff or your own repository.
If there is more demand later then we can reconsider your offer to donate
this code to Wicket itself.

Thank you!


>
>
> Thanks,
> John Sarman
>