diff --git a/src/main/java/se/su/dsv/scipro/HomePage.java b/src/main/java/se/su/dsv/scipro/HomePage.java index 708cc3f888..355c406061 100644 --- a/src/main/java/se/su/dsv/scipro/HomePage.java +++ b/src/main/java/se/su/dsv/scipro/HomePage.java @@ -4,13 +4,16 @@ import org.apache.wicket.PageParameters; import org.apache.wicket.markup.html.WebPage; import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import se.su.dsv.scipro.basepages.BasePage; +import se.su.dsv.scipro.basepages.PublicPage; import se.su.dsv.scipro.pages.EventPage; +import se.su.dsv.scipro.security.auth.Authorization; /** - * @author Richard Wilkinson - richard.wilkinson@jweekend.com + * @author Martin Peters - mpeters@dsv.su.se * */ -public class HomePage extends WebPage { +public class HomePage extends PublicPage { private static final long serialVersionUID = 1L; diff --git a/src/main/java/se/su/dsv/scipro/SciProApplication.java b/src/main/java/se/su/dsv/scipro/SciProApplication.java index ebfb846d3f..93c47a18e9 100644 --- a/src/main/java/se/su/dsv/scipro/SciProApplication.java +++ b/src/main/java/se/su/dsv/scipro/SciProApplication.java @@ -6,12 +6,16 @@ import org.apache.wicket.Page; import org.apache.wicket.Request; import org.apache.wicket.Response; import org.apache.wicket.Session; +import org.apache.wicket.authorization.strategies.CompoundAuthorizationStrategy; import org.apache.wicket.protocol.http.WebApplication; import org.apache.wicket.spring.injection.annot.SpringComponentInjector; import se.su.dsv.scipro.loginlogout.pages.LoginPage; import se.su.dsv.scipro.loginlogout.pages.LogoutPage; import se.su.dsv.scipro.pages.EventPage; +import se.su.dsv.scipro.security.auth.ComponentSecurityLogger; +import se.su.dsv.scipro.security.auth.MetaDataActionStrategy; +import se.su.dsv.scipro.security.auth.RoleBasedAuthorizationStrategy; /** * Application object for your web application. If you want to run this application without deploying, run the Start class. @@ -53,6 +57,13 @@ public class SciProApplication extends WebApplication { mountBookmarkablePage("logout", LogoutPage.class); addComponentInstantiationListener(getSpringInjector()); + + CompoundAuthorizationStrategy cas = new CompoundAuthorizationStrategy(); + cas.add(new RoleBasedAuthorizationStrategy()); + cas.add(new MetaDataActionStrategy()); + getSecuritySettings().setAuthorizationStrategy(cas); + + getSecuritySettings().setUnauthorizedComponentInstantiationListener(new ComponentSecurityLogger()); } diff --git a/src/main/java/se/su/dsv/scipro/SciProSession.java b/src/main/java/se/su/dsv/scipro/SciProSession.java index e62744d6d1..e73862f26a 100644 --- a/src/main/java/se/su/dsv/scipro/SciProSession.java +++ b/src/main/java/se/su/dsv/scipro/SciProSession.java @@ -1,5 +1,7 @@ package se.su.dsv.scipro; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; import javax.security.auth.login.FailedLoginException; @@ -15,9 +17,17 @@ import org.apache.wicket.protocol.http.WebRequest; import org.apache.wicket.protocol.http.WebSession; import org.apache.wicket.spring.injection.annot.SpringBean; -import se.su.dsv.scipro.auth.Authenticator; import se.su.dsv.scipro.data.dao.interfaces.UserDao; import se.su.dsv.scipro.data.dataobjects.User; +import se.su.dsv.scipro.security.auth.Authenticator; +import se.su.dsv.scipro.security.auth.roles.Admin; +import se.su.dsv.scipro.security.auth.roles.DefaultRole; +import se.su.dsv.scipro.security.auth.roles.Employee; +import se.su.dsv.scipro.security.auth.roles.External; +import se.su.dsv.scipro.security.auth.roles.Role; +import se.su.dsv.scipro.security.auth.roles.Roles; +import se.su.dsv.scipro.security.auth.roles.Student; +import se.su.dsv.scipro.security.auth.roles.SysAdmin; @@ -30,7 +40,7 @@ public class SciProSession extends WebSession { @SpringBean private UserDao userDao; - //private List<Role> roles = new ArrayList<Role>(); + private List<Role> roles = new ArrayList<Role>(); private User user = null; private String loggedInIdentity = null; @@ -119,36 +129,39 @@ public class SciProSession extends WebSession { /* * Here we switch the logged in user to be that of the person chosen be the logged in admin */ - /* + if(userDao.isAdmin(user) && loggedInAsUsername != null){ - this.user = userDao.getUserByUserName(loggedInAsUsername); + this.user = userDao.getUserByUsername(loggedInAsUsername); if( user == null) - throw new NullPointerException("No user with this username found in the database, despite successful authentication"); - + throw new NullPointerException("No user with this username found in the database, despite successful authentication"); } - if(userDao.isAuthor(user)){ - rolesString = rolesString +"AUTHOR,"; - roles.add(new AuthorRole()); + if(userDao.isStudent(user)){ + roles.add(new Student()); } - if(userDao.isSupervisor(user)){ - rolesString = rolesString +"SUPERVISOR,"; - roles.add(new SupervisorRole()); + if(userDao.isExternal(user)){ + roles.add(new External()); + } + if(userDao.isEmployee(user)){ + roles.add(new Employee()); } if(userDao.isAdmin(user)){ - rolesString = rolesString +"ADMIN,"; - roles.add(new AdminRole()); + roles.add(new Admin()); } + if(userDao.isSysadmin(user)){ + roles.add(new SysAdmin()); + } + if(roles.isEmpty()){ roles.add(new DefaultRole()); } - */ + Logger logger = Logger.getLogger("Application"); logger.log(Level.INFO, "User: "+getLoggedInIdentity()+ " logged in as: "+user.getFirstName()+" "+user.getLastName()+" "+user.getUserNames()); return true; } catch (FailedLoginException e) { - //e.printStackTrace(); + e.printStackTrace(); } catch (LoginException e) { - //e.printStackTrace(); + e.printStackTrace(); } return false; } @@ -157,6 +170,13 @@ public class SciProSession extends WebSession { public String toString(){ return loggedInIdentity;//+" Roles: "+rolesString; } - + + public boolean authorizedForRole(Roles role) { + for(Role existingRole : roles){ + if(existingRole.authorizedForRole(role)) + return true; + } + return false; + } } diff --git a/src/main/java/se/su/dsv/scipro/basepages/BasePage.java b/src/main/java/se/su/dsv/scipro/basepages/BasePage.java index 30a6926997..a596882157 100644 --- a/src/main/java/se/su/dsv/scipro/basepages/BasePage.java +++ b/src/main/java/se/su/dsv/scipro/basepages/BasePage.java @@ -4,6 +4,10 @@ import org.apache.wicket.PageParameters; import org.apache.wicket.markup.html.CSSPackageResource; import org.apache.wicket.markup.html.WebPage; +import se.su.dsv.scipro.security.auth.Authorization; +import se.su.dsv.scipro.security.auth.roles.Roles; + +@Authorization(requiresLoggedInUser = true, authorizedRoles = {Roles.STUDENT, Roles.ADMIN, Roles.EMPLOYEE, Roles.EXTERNAL, Roles.SYSADMIN}) public abstract class BasePage extends WebPage { public BasePage(){ @@ -14,12 +18,9 @@ public abstract class BasePage extends WebPage { super(pp); } - public static String ADMIN_CSS = "admin"; - public static String SUPERVISOR_CSS = "supervisor"; - public static String AUTHOR_CSS = "author"; - public static String GLOBAL_CSS = "global"; - public static String POPUP_CSS = "popup"; - public static String CSS_SUFFIX = ".css"; + public final static String GLOBAL_CSS = "global"; + public final static String POPUP_CSS = "popup"; + public final static String CSS_SUFFIX = ".css"; protected void setCss(String css){ String languageCode = ""; diff --git a/src/main/java/se/su/dsv/scipro/basepages/PublicPage.java b/src/main/java/se/su/dsv/scipro/basepages/PublicPage.java new file mode 100644 index 0000000000..efca7d43f9 --- /dev/null +++ b/src/main/java/se/su/dsv/scipro/basepages/PublicPage.java @@ -0,0 +1,18 @@ +package se.su.dsv.scipro.basepages; + +import org.apache.wicket.PageParameters; + +import se.su.dsv.scipro.security.auth.Authorization; + +@Authorization(requiresLoggedInUser = false) +public abstract class PublicPage extends BasePage { + + public PublicPage(){ + super(); + } + + public PublicPage(PageParameters pp){ + super(pp); + } + +} diff --git a/src/main/java/se/su/dsv/scipro/data/dao/interfaces/UserDao.java b/src/main/java/se/su/dsv/scipro/data/dao/interfaces/UserDao.java index 04e9615110..6399cab4b9 100644 --- a/src/main/java/se/su/dsv/scipro/data/dao/interfaces/UserDao.java +++ b/src/main/java/se/su/dsv/scipro/data/dao/interfaces/UserDao.java @@ -10,5 +10,14 @@ public interface UserDao extends LazyDeleteDao<User> { public User getUserByEmail(final String emailAddress); + public boolean isStudent(final User user); + + public boolean isExternal(final User user); + + public boolean isEmployee(final User user); + + public boolean isAdmin(final User user); + + public boolean isSysadmin(final User user); } diff --git a/src/main/java/se/su/dsv/scipro/data/dao/jpa/UserDaoJPAImp.java b/src/main/java/se/su/dsv/scipro/data/dao/jpa/UserDaoJPAImp.java index f980054707..77ad4f75fd 100644 --- a/src/main/java/se/su/dsv/scipro/data/dao/jpa/UserDaoJPAImp.java +++ b/src/main/java/se/su/dsv/scipro/data/dao/jpa/UserDaoJPAImp.java @@ -67,4 +67,29 @@ public class UserDaoJPAImp extends LazyDeleteAbstractDaoJPAImpl<User> implements }); } + public boolean isStudent(final User user) { + // TODO Auto-generated method stub + return true; + } + + public boolean isExternal(final User user) { + // TODO Auto-generated method stub + return false; + } + + public boolean isEmployee(final User user) { + // TODO Auto-generated method stub + return false; + } + + public boolean isAdmin(final User user) { + // TODO Auto-generated method stub + return false; + } + + public boolean isSysadmin(final User user) { + // TODO Auto-generated method stub + return false; + } + } diff --git a/src/main/java/se/su/dsv/scipro/data/dataobjects/Event.java b/src/main/java/se/su/dsv/scipro/data/dataobjects/Event.java index 6511e31ae3..bcdb8338ff 100644 --- a/src/main/java/se/su/dsv/scipro/data/dataobjects/Event.java +++ b/src/main/java/se/su/dsv/scipro/data/dataobjects/Event.java @@ -9,7 +9,7 @@ import javax.persistence.Table; * */ @Entity -@Table(name="Event") +@Table(name="event") public class Event extends DomainObject { private static final long serialVersionUID = 2959377496669050427L; diff --git a/src/main/java/se/su/dsv/scipro/data/dataobjects/User.java b/src/main/java/se/su/dsv/scipro/data/dataobjects/User.java index 1b1dea043b..ef7ad0a641 100644 --- a/src/main/java/se/su/dsv/scipro/data/dataobjects/User.java +++ b/src/main/java/se/su/dsv/scipro/data/dataobjects/User.java @@ -18,7 +18,7 @@ import org.hibernate.annotations.CacheConcurrencyStrategy; * */ @Entity -@Table(name="User") +@Table(name="user") @Cacheable(true) @Cache(usage= CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) //Hibernate specific public class User extends LazyDeletableDomainObject { diff --git a/src/main/java/se/su/dsv/scipro/data/dataobjects/Username.java b/src/main/java/se/su/dsv/scipro/data/dataobjects/Username.java index 88a58f31e5..2926eef4bb 100644 --- a/src/main/java/se/su/dsv/scipro/data/dataobjects/Username.java +++ b/src/main/java/se/su/dsv/scipro/data/dataobjects/Username.java @@ -19,7 +19,7 @@ import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable(true) @Cache(usage= CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) //Hibernate specific -@Table(name="Username", uniqueConstraints={@UniqueConstraint(columnNames={"username","realm"})}) +@Table(name="username", uniqueConstraints={@UniqueConstraint(columnNames={"username","realm"})}) public class Username extends DomainObject { private static final long serialVersionUID = -2043481766263425696L; diff --git a/src/main/java/se/su/dsv/scipro/loginlogout/pages/LoginPage.java b/src/main/java/se/su/dsv/scipro/loginlogout/pages/LoginPage.java index 8a19ab198c..008f324528 100644 --- a/src/main/java/se/su/dsv/scipro/loginlogout/pages/LoginPage.java +++ b/src/main/java/se/su/dsv/scipro/loginlogout/pages/LoginPage.java @@ -5,7 +5,7 @@ import org.apache.wicket.RestartResponseException; import se.su.dsv.scipro.HomePage; import se.su.dsv.scipro.SciProSession; -import se.su.dsv.scipro.basepages.BasePage; +import se.su.dsv.scipro.basepages.PublicPage; import se.su.dsv.scipro.loginlogout.panels.LoginPanel; /** @@ -13,16 +13,10 @@ import se.su.dsv.scipro.loginlogout.panels.LoginPanel; * @param pp the page parameters * @author Martin Peters - mpeters@dsv.su.se */ -public class LoginPage extends BasePage { +public class LoginPage extends PublicPage { public LoginPage(final PageParameters pp) throws Exception { - - //Redirect to HomePage if user is authenticated - if(SciProSession.get().isLoggedIn()){ - getRequestCycle().setRedirect(true); - throw new RestartResponseException(HomePage.class); - } - + add(new LoginPanel("signInPanel", true)); } diff --git a/src/main/java/se/su/dsv/scipro/loginlogout/pages/LogoutPage.java b/src/main/java/se/su/dsv/scipro/loginlogout/pages/LogoutPage.java index b34466e233..8c5d34259b 100644 --- a/src/main/java/se/su/dsv/scipro/loginlogout/pages/LogoutPage.java +++ b/src/main/java/se/su/dsv/scipro/loginlogout/pages/LogoutPage.java @@ -5,14 +5,13 @@ import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.link.BookmarkablePageLink; import se.su.dsv.scipro.SciProSession; -import se.su.dsv.scipro.basepages.BasePage; +import se.su.dsv.scipro.basepages.PublicPage; -public class LogoutPage extends BasePage { +public class LogoutPage extends PublicPage { public LogoutPage(final PageParameters parameters){ - SciProSession session = SciProSession.get(); - session.invalidate(); + SciProSession.get().invalidate(); add(new Label("message", "You are now logged out. Log back in ")); BookmarkablePageLink<Void> bml = new BookmarkablePageLink<Void>("loginLink", LoginPage.class); diff --git a/src/main/java/se/su/dsv/scipro/pages/EventPage.java b/src/main/java/se/su/dsv/scipro/pages/EventPage.java index 4baad11662..b09dcc3e6f 100644 --- a/src/main/java/se/su/dsv/scipro/pages/EventPage.java +++ b/src/main/java/se/su/dsv/scipro/pages/EventPage.java @@ -16,23 +16,35 @@ import org.apache.wicket.model.CompoundPropertyModel; import org.apache.wicket.model.PropertyModel; import org.apache.wicket.spring.injection.annot.SpringBean; +import se.su.dsv.scipro.basepages.BasePage; import se.su.dsv.scipro.data.dao.interfaces.EventDao; import se.su.dsv.scipro.data.dataobjects.Event; +import se.su.dsv.scipro.security.auth.Authorization; +import se.su.dsv.scipro.security.auth.Authorization.ON_AUTH_FAIL; +import se.su.dsv.scipro.security.auth.ComponentSecurity; +import se.su.dsv.scipro.security.auth.MetaDataActionStrategy; +import se.su.dsv.scipro.security.auth.SecurityFailAction; +import se.su.dsv.scipro.security.auth.roles.Roles; +import se.su.dsv.scipro.security.auth.roles.Student; /** * @author Richard Wilkinson - richard.wilkinson@jweekend.com * */ -public class EventPage extends WebPage { +public class EventPage extends BasePage { @SpringBean private EventDao eventDao; + Form<Event> eventForm; + public EventPage(final PageParameters pp) { - Form<Event> eventForm = new Form<Event>("eventForm", new CompoundPropertyModel<Event>(new Event())); + eventForm = new Form<Event>("eventForm", new CompoundPropertyModel<Event>(new Event())); eventForm.add(new TextField<String>("title").setRequired(true)); eventForm.add(new TextField<String>("location").setRequired(true)); + eventForm.setMetaData(MetaDataActionStrategy.AUTHORIZED_ROLES, new ComponentSecurity(Roles.EMPLOYEE, SecurityFailAction.DISABLE)); + final WebMarkupContainer wmc = new WebMarkupContainer("listContainer"); wmc.add(new ListView<Event>("list", new PropertyModel<List<Event>>(this, "eventDao.findAll")){ @@ -68,4 +80,5 @@ public class EventPage extends WebPage { add(eventForm); } + } diff --git a/src/main/java/se/su/dsv/scipro/auth/Authenticator.java b/src/main/java/se/su/dsv/scipro/security/auth/Authenticator.java similarity index 98% rename from src/main/java/se/su/dsv/scipro/auth/Authenticator.java rename to src/main/java/se/su/dsv/scipro/security/auth/Authenticator.java index be49ade48a..4c53a0580d 100644 --- a/src/main/java/se/su/dsv/scipro/auth/Authenticator.java +++ b/src/main/java/se/su/dsv/scipro/security/auth/Authenticator.java @@ -1,4 +1,4 @@ -package se.su.dsv.scipro.auth; +package se.su.dsv.scipro.security.auth; import javax.security.auth.login.FailedLoginException; import javax.security.auth.login.LoginContext; diff --git a/src/main/java/se/su/dsv/scipro/security/auth/Authorization.java b/src/main/java/se/su/dsv/scipro/security/auth/Authorization.java new file mode 100644 index 0000000000..a78045c00c --- /dev/null +++ b/src/main/java/se/su/dsv/scipro/security/auth/Authorization.java @@ -0,0 +1,40 @@ +package se.su.dsv.scipro.security.auth; + +import java.lang.annotation.*; + +import se.su.dsv.scipro.security.auth.roles.Roles; +/** + * + * @author Martin Peters - mpeters@dsv.su.se + * @author Niklas Herder for base code + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +public @interface Authorization { + + enum ON_AUTH_FAIL { + /** + * Disable the component, but show + */ + DISABLE_COMPONENT, + /** + * Hide the component + */ + HIDE_COMPONENT, + } + + + /** + * Which action to take on this component when authorization fails. + */ + ON_AUTH_FAIL actionOnAuthFail() default ON_AUTH_FAIL.HIDE_COMPONENT; + + /** + * Whether the component requires a logged in user + */ + boolean requiresLoggedInUser() default true; + + Roles[] authorizedRoles() default {Roles.SYSADMIN}; + +} diff --git a/src/main/java/se/su/dsv/scipro/security/auth/ComponentSecurity.java b/src/main/java/se/su/dsv/scipro/security/auth/ComponentSecurity.java new file mode 100644 index 0000000000..4dfaf167be --- /dev/null +++ b/src/main/java/se/su/dsv/scipro/security/auth/ComponentSecurity.java @@ -0,0 +1,52 @@ +package se.su.dsv.scipro.security.auth; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import se.su.dsv.scipro.security.auth.roles.Roles; +/** + * + * @author Martin Peters - mpeters@dsv.su.se + * + */ +public class ComponentSecurity implements Serializable { + + private static final long serialVersionUID = 1L; + + private final ArrayList<Roles> authorizedRoles = new ArrayList<Roles>(); + private SecurityFailAction failAction; + + public ComponentSecurity(Roles role){ + this(role, SecurityFailAction.REMOVE); + } + + public ComponentSecurity(Roles role, SecurityFailAction action){ + authorizedRoles.add(role); + if( action == null ) + throw new IllegalArgumentException("FailAction may not be null"); + failAction = action; + } + + public ComponentSecurity(Roles[] roles){ + this(roles, SecurityFailAction.REMOVE); + } + + public ComponentSecurity(Roles[] roles, SecurityFailAction action) { + authorizedRoles.addAll(Arrays.asList(roles)); + if( action == null ) + throw new IllegalArgumentException("FailAction may not be null"); + failAction = action; + } + + public SecurityFailAction getFailAction(){ + return failAction; + } + + public List<Roles> getAuthorizedRoles(){ + //For security-concerns we don't return the mutable list but a copy + return new ArrayList<Roles>(authorizedRoles); + } + +} diff --git a/src/main/java/se/su/dsv/scipro/security/auth/ComponentSecurityLogger.java b/src/main/java/se/su/dsv/scipro/security/auth/ComponentSecurityLogger.java new file mode 100644 index 0000000000..6adaeebd56 --- /dev/null +++ b/src/main/java/se/su/dsv/scipro/security/auth/ComponentSecurityLogger.java @@ -0,0 +1,25 @@ +package se.su.dsv.scipro.security.auth; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.wicket.Component; +import org.apache.wicket.RestartResponseAtInterceptPageException; +import org.apache.wicket.authorization.IUnauthorizedComponentInstantiationListener; + +import se.su.dsv.scipro.SciProApplication; +import se.su.dsv.scipro.SciProSession; +/** + * + * @author Martin Peters - mpeters@dsv.su.se + * + */ +public class ComponentSecurityLogger implements IUnauthorizedComponentInstantiationListener { + + public void onUnauthorizedInstantiation(Component component) { + Logger logger = Logger.getLogger("Application"); + logger.log(Level.WARN, "User: "+SciProSession.get().getLoggedInIdentity()+ " attempted to instantiate unauthorized page: "+component.getClass()); + throw new RestartResponseAtInterceptPageException(SciProApplication.get().getApplicationSettings().getAccessDeniedPage()); + + } + +} diff --git a/src/main/java/se/su/dsv/scipro/auth/LoginCallbackHandler.java b/src/main/java/se/su/dsv/scipro/security/auth/LoginCallbackHandler.java similarity index 97% rename from src/main/java/se/su/dsv/scipro/auth/LoginCallbackHandler.java rename to src/main/java/se/su/dsv/scipro/security/auth/LoginCallbackHandler.java index 2ba3547580..31aa167f7a 100644 --- a/src/main/java/se/su/dsv/scipro/auth/LoginCallbackHandler.java +++ b/src/main/java/se/su/dsv/scipro/security/auth/LoginCallbackHandler.java @@ -1,4 +1,4 @@ -package se.su.dsv.scipro.auth; +package se.su.dsv.scipro.security.auth; import java.io.IOException; import javax.security.auth.callback.Callback; diff --git a/src/main/java/se/su/dsv/scipro/security/auth/MetaDataActionStrategy.java b/src/main/java/se/su/dsv/scipro/security/auth/MetaDataActionStrategy.java new file mode 100644 index 0000000000..0f1963ad34 --- /dev/null +++ b/src/main/java/se/su/dsv/scipro/security/auth/MetaDataActionStrategy.java @@ -0,0 +1,61 @@ +package se.su.dsv.scipro.security.auth; + +import java.util.List; + +import org.apache.wicket.Component; +import org.apache.wicket.MetaDataKey; +import org.apache.wicket.authorization.Action; +import org.apache.wicket.authorization.IAuthorizationStrategy; + +import se.su.dsv.scipro.SciProSession; +import se.su.dsv.scipro.security.auth.roles.Roles; +/** + * + * @author Martin Peters - mpeters@dsv.su.se + * Checks for actionAuthorization of components based on component-metadata + */ +public class MetaDataActionStrategy implements IAuthorizationStrategy { + + public static final MetaDataKey<ComponentSecurity> AUTHORIZED_ROLES = new MetaDataKey<ComponentSecurity>() { + + private static final long serialVersionUID = 1L; + }; + + public boolean isActionAuthorized(final Component component, final Action action) { + //Get metadata from component, if none return true for any action + ComponentSecurity cs = component.getMetaData(AUTHORIZED_ROLES); + if( cs != null ){ + //Check if user is authorized for this role + List<Roles> authorizedRoles = cs.getAuthorizedRoles(); + for(Roles role : authorizedRoles){ + if(SciProSession.get().authorizedForRole(role)) + return true; + } + //If we've left the loop without a return then handle different kinds of failure + if(action.equals(Component.RENDER)){ + if( cs.getFailAction().equals(SecurityFailAction.REMOVE)) + return false; + else + return true; + } + else if(action.equals(Component.ENABLE)){ + //A value of ENABLE = false should be the only logical option left at this point + return false; + /* If more fail actions were implemented consider this code + if(cs.getFailAction().equals(SecurityFailAction.DISABLE)) + return false; + else + return true; + */ + } else + throw new IllegalStateException("Should be unreachable code, someone broke something! Unrecognized action: "+action); + } + return true; + } + + public <T extends Component> boolean isInstantiationAuthorized(final Class<T> arg0) { + // Doesn't care about instantiation, only cares about action authorization + return true; + } + +} diff --git a/src/main/java/se/su/dsv/scipro/security/auth/RoleBasedAuthorizationStrategy.java b/src/main/java/se/su/dsv/scipro/security/auth/RoleBasedAuthorizationStrategy.java new file mode 100644 index 0000000000..b9d402e90c --- /dev/null +++ b/src/main/java/se/su/dsv/scipro/security/auth/RoleBasedAuthorizationStrategy.java @@ -0,0 +1,96 @@ +package se.su.dsv.scipro.security.auth; + +import org.apache.wicket.Component; +import org.apache.wicket.RestartResponseAtInterceptPageException; +import org.apache.wicket.authorization.Action; +import org.apache.wicket.authorization.IAuthorizationStrategy; +import org.apache.wicket.markup.html.link.BookmarkablePageLink; + +import se.su.dsv.scipro.SciProSession; +import se.su.dsv.scipro.loginlogout.pages.LoginPage; +import se.su.dsv.scipro.security.auth.roles.Roles; +/** + * + * @author Martin Peters - mpeters@dsv.su.se + * @author Niklas Herder for inspiration + */ +public class RoleBasedAuthorizationStrategy implements IAuthorizationStrategy { + + public boolean isActionAuthorized(Component component, Action action) { + + boolean ok = false; + Class<? extends Component> authRequired = null; + if (component instanceof BookmarkablePageLink<?>) { + //If it's a link: check permissions for the component the link refers to. + BookmarkablePageLink<?> bookmarkablePageLink = (BookmarkablePageLink<?>) component; + authRequired = bookmarkablePageLink.getPageClass(); + } else { + //Otherwise check auth for the compoenent + authRequired = component.getClass(); + } + //Get the annotation from the class if there is one + Authorization annotation = authRequired.getAnnotation(Authorization.class); + //Not annotated classes aren't checked further + if( annotation == null) + ok = true; + else { + //Check if it's a "public page" or not + if ( !annotation.requiresLoggedInUser() ) + ok = true; + else { + //Check for presence of login and if present check for authorization + if( SciProSession.get().isLoggedIn() ){//&& annotation.authorizedRoles().length > 0){ If no roles added, ok will be false + for( Roles role : annotation.authorizedRoles() ){ + if( SciProSession.get().authorizedForRole(role) ){ + ok = true; + break; + } + } + } + } + //If not ok so far check what to do on fail + if(!ok){ + if(action.equals(Component.RENDER)){ + if( annotation.actionOnAuthFail() == Authorization.ON_AUTH_FAIL.HIDE_COMPONENT) + ok = false; + else + ok = true; + } else if (action.equals(Component.ENABLE)){ + if( annotation.actionOnAuthFail() == Authorization.ON_AUTH_FAIL.DISABLE_COMPONENT) + ok = false; + else + ok = true; + } else + throw new IllegalStateException("Action "+action.getName()+ " is unknown"); + }//Auth failed, what to do on fail? + }//There was an annotation present + + //TODO: Remove this println + System.out.println("RoleBasedAuthorisationStrategy says authorized: "+ok+" for action "+action.getName()+" on component "+component.getClass()); + return ok; + } + + public <T extends Component> boolean isInstantiationAuthorized(Class<T> componentClass) { + + //Not annotated classes aren't checked further + Authorization annotation = (Authorization) componentClass.getAnnotation(Authorization.class); + if (annotation != null) { + + //If component doesn't require login anyone may do it any time + if( !annotation.requiresLoggedInUser() ) + return true; + //If page requires login and user isn't logged in, send them to login page first + if( annotation.requiresLoggedInUser() && !SciProSession.get().isLoggedIn()) + throw new RestartResponseAtInterceptPageException(LoginPage.class); + //Check the users role for authorization to instantiate the component + for(Roles role : annotation.authorizedRoles()){ + if( SciProSession.get().authorizedForRole(role) ) + return true; + } + //No roles were added to the annotation or user was not authorized for the roles that were added + return false; + } + return true; + } + +} diff --git a/src/main/java/se/su/dsv/scipro/security/auth/SecurityFailAction.java b/src/main/java/se/su/dsv/scipro/security/auth/SecurityFailAction.java new file mode 100644 index 0000000000..17b5b32156 --- /dev/null +++ b/src/main/java/se/su/dsv/scipro/security/auth/SecurityFailAction.java @@ -0,0 +1,17 @@ +package se.su.dsv.scipro.security.auth; +/** + * @author Martin Peters - mpeters@dsv.su.se + * @author Niklas Herder for inspiration + * Enumeration of different actions to take when the actionAuthorization fails on a component + */ +public enum SecurityFailAction { + /* + * Disable a component, makes links unclickable, buttons 'grey' etc. + */ + DISABLE, + /* + * Completely removes components instead of rendering them + */ + REMOVE + +} diff --git a/src/main/java/se/su/dsv/scipro/security/auth/roles/Admin.java b/src/main/java/se/su/dsv/scipro/security/auth/roles/Admin.java new file mode 100644 index 0000000000..d2a40a42c9 --- /dev/null +++ b/src/main/java/se/su/dsv/scipro/security/auth/roles/Admin.java @@ -0,0 +1,20 @@ +package se.su.dsv.scipro.security.auth.roles; + +public class Admin extends DefaultRole { + + @Override + public boolean authorizedForRole(Roles role) { + switch(role) { + case SYSADMIN: + return false; + default: + return true; + } + } + + @Override + public String toString() { + return "Admin"; + } + +} diff --git a/src/main/java/se/su/dsv/scipro/security/auth/roles/DefaultRole.java b/src/main/java/se/su/dsv/scipro/security/auth/roles/DefaultRole.java new file mode 100644 index 0000000000..85f9fb39f8 --- /dev/null +++ b/src/main/java/se/su/dsv/scipro/security/auth/roles/DefaultRole.java @@ -0,0 +1,9 @@ +package se.su.dsv.scipro.security.auth.roles; + +public class DefaultRole implements Role { + + public boolean authorizedForRole(Roles role) { + return false; + } + +} diff --git a/src/main/java/se/su/dsv/scipro/security/auth/roles/Employee.java b/src/main/java/se/su/dsv/scipro/security/auth/roles/Employee.java new file mode 100644 index 0000000000..aac1fd27e7 --- /dev/null +++ b/src/main/java/se/su/dsv/scipro/security/auth/roles/Employee.java @@ -0,0 +1,24 @@ +package se.su.dsv.scipro.security.auth.roles; + +public class Employee extends DefaultRole { + + @Override + public boolean authorizedForRole(Roles role) { + switch(role) { + case EMPLOYEE: + return true; + case EXTERNAL: + return true; + case STUDENT: + return true; + default: + return false; + } + } + + @Override + public String toString() { + return "Employee"; + } + +} diff --git a/src/main/java/se/su/dsv/scipro/security/auth/roles/External.java b/src/main/java/se/su/dsv/scipro/security/auth/roles/External.java new file mode 100644 index 0000000000..12f4843bb9 --- /dev/null +++ b/src/main/java/se/su/dsv/scipro/security/auth/roles/External.java @@ -0,0 +1,20 @@ +package se.su.dsv.scipro.security.auth.roles; + +public class External extends DefaultRole { + + @Override + public boolean authorizedForRole(Roles role) { + switch(role) { + case EXTERNAL: + return true; + default: + return false; + } + } + + @Override + public String toString() { + return "External"; + } + +} diff --git a/src/main/java/se/su/dsv/scipro/security/auth/roles/Role.java b/src/main/java/se/su/dsv/scipro/security/auth/roles/Role.java new file mode 100644 index 0000000000..2fe32b35c7 --- /dev/null +++ b/src/main/java/se/su/dsv/scipro/security/auth/roles/Role.java @@ -0,0 +1,7 @@ +package se.su.dsv.scipro.security.auth.roles; + +public interface Role { + + public boolean authorizedForRole(Roles role); + +} diff --git a/src/main/java/se/su/dsv/scipro/security/auth/roles/Roles.java b/src/main/java/se/su/dsv/scipro/security/auth/roles/Roles.java new file mode 100644 index 0000000000..7b1420e0c9 --- /dev/null +++ b/src/main/java/se/su/dsv/scipro/security/auth/roles/Roles.java @@ -0,0 +1,26 @@ +package se.su.dsv.scipro.security.auth.roles; + +public enum Roles { + + /* + * For students + */ + STUDENT, + /* + * Externals could be given supervisional roles but no administrative rights + */ + EXTERNAL, + /* + * Employees people might have some administrative rights and may have supervisional roles + */ + EMPLOYEE, + /* + * Can do most things + */ + ADMIN, + /* + * Can do everything + */ + SYSADMIN + +} diff --git a/src/main/java/se/su/dsv/scipro/security/auth/roles/Student.java b/src/main/java/se/su/dsv/scipro/security/auth/roles/Student.java new file mode 100644 index 0000000000..b81e186e55 --- /dev/null +++ b/src/main/java/se/su/dsv/scipro/security/auth/roles/Student.java @@ -0,0 +1,20 @@ +package se.su.dsv.scipro.security.auth.roles; + +public class Student extends DefaultRole { + + @Override + public boolean authorizedForRole(Roles role) { + switch(role) { + case STUDENT: + return true; + default: + return false; + } + } + + @Override + public String toString() { + return "Student"; + } + +} diff --git a/src/main/java/se/su/dsv/scipro/security/auth/roles/SysAdmin.java b/src/main/java/se/su/dsv/scipro/security/auth/roles/SysAdmin.java new file mode 100644 index 0000000000..a0f342d304 --- /dev/null +++ b/src/main/java/se/su/dsv/scipro/security/auth/roles/SysAdmin.java @@ -0,0 +1,15 @@ +package se.su.dsv.scipro.security.auth.roles; + +public class SysAdmin extends DefaultRole { + + @Override + public boolean authorizedForRole(Roles role) { + return true; + } + + @Override + public String toString() { + return "SysAdmin"; + } + +}