diff --git a/src/main/java/com/commafeed/backend/StartupBean.java b/src/main/java/com/commafeed/backend/StartupBean.java index fd7c9cff..b245f264 100644 --- a/src/main/java/com/commafeed/backend/StartupBean.java +++ b/src/main/java/com/commafeed/backend/StartupBean.java @@ -16,7 +16,9 @@ import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.FeedCategory; import com.commafeed.backend.model.FeedSubscription; import com.commafeed.backend.model.User; +import com.commafeed.backend.model.UserRole; import com.commafeed.backend.security.PasswordEncryptionService; +import com.commafeed.backend.security.Role; @Startup @Singleton @@ -48,6 +50,8 @@ public class StartupBean { User user = new User(); byte[] salt = encryptionService.generateSalt(); user.setName("admin"); + user.getRoles().add(new UserRole(user, Role.ADMIN)); + user.getRoles().add(new UserRole(user, Role.USER)); user.setSalt(salt); user.setPassword(encryptionService.getEncryptedPassword("admin", salt)); @@ -56,6 +60,7 @@ public class StartupBean { User testUser = new User(); byte[] saltTest = encryptionService.generateSalt(); testUser.setName("test"); + testUser.getRoles().add(new UserRole(testUser, Role.USER)); testUser.setSalt(saltTest); testUser.setPassword(encryptionService.getEncryptedPassword("test", saltTest)); diff --git a/src/main/java/com/commafeed/backend/dao/UserRoleService.java b/src/main/java/com/commafeed/backend/dao/UserRoleService.java new file mode 100644 index 00000000..c7fa13a7 --- /dev/null +++ b/src/main/java/com/commafeed/backend/dao/UserRoleService.java @@ -0,0 +1,23 @@ +package com.commafeed.backend.dao; + +import java.util.List; + +import javax.ejb.Stateless; + +import com.commafeed.backend.model.User; +import com.commafeed.backend.model.UserRole; +import com.commafeed.frontend.utils.ModelFactory.MF; +import com.google.common.collect.Lists; + +@SuppressWarnings("serial") +@Stateless +public class UserRoleService extends GenericDAO { + + public List getRoles(User user) { + List list = Lists.newArrayList(); + for (UserRole role : findByField(MF.i(proxy().getUser()), user)) { + list.add(role.getRole()); + } + return list; + } +} diff --git a/src/main/java/com/commafeed/backend/model/User.java b/src/main/java/com/commafeed/backend/model/User.java index 712f4e9a..356a219f 100644 --- a/src/main/java/com/commafeed/backend/model/User.java +++ b/src/main/java/com/commafeed/backend/model/User.java @@ -1,9 +1,15 @@ package com.commafeed.backend.model; +import java.util.Set; + +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.OneToMany; import javax.persistence.Table; +import com.google.common.collect.Sets; + @Entity @Table(name = "USERS") @SuppressWarnings("serial") @@ -18,6 +24,9 @@ public class User extends AbstractModel { @Column(length = 8) private byte[] salt; + @OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST) + private Set roles = Sets.newHashSet(); + public String getName() { return name; } @@ -42,4 +51,12 @@ public class User extends AbstractModel { this.salt = salt; } + public Set getRoles() { + return roles; + } + + public void setRoles(Set roles) { + this.roles = roles; + } + } diff --git a/src/main/java/com/commafeed/backend/model/UserRole.java b/src/main/java/com/commafeed/backend/model/UserRole.java new file mode 100644 index 00000000..0f9c2522 --- /dev/null +++ b/src/main/java/com/commafeed/backend/model/UserRole.java @@ -0,0 +1,46 @@ +package com.commafeed.backend.model; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +@Entity +@Table(name = "USERROLES") +@SuppressWarnings("serial") +public class UserRole extends AbstractModel { + + @OneToOne + @JoinColumn(name = "user_id") + private User user; + + @Column(name = "roleName") + private String role; + + public UserRole() { + + } + + public UserRole(User user, String role) { + this.user = user; + this.role = role; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + +} diff --git a/src/main/java/com/commafeed/frontend/pages/auth/Role.java b/src/main/java/com/commafeed/backend/security/Role.java similarity index 69% rename from src/main/java/com/commafeed/frontend/pages/auth/Role.java rename to src/main/java/com/commafeed/backend/security/Role.java index 9cc5d84c..f3639cec 100644 --- a/src/main/java/com/commafeed/frontend/pages/auth/Role.java +++ b/src/main/java/com/commafeed/backend/security/Role.java @@ -1,4 +1,4 @@ -package com.commafeed.frontend.pages.auth; +package com.commafeed.backend.security; public class Role { public static final String USER = "user"; diff --git a/src/main/java/com/commafeed/frontend/CommaFeedSession.java b/src/main/java/com/commafeed/frontend/CommaFeedSession.java index 16cd6f83..f47cd27f 100644 --- a/src/main/java/com/commafeed/frontend/CommaFeedSession.java +++ b/src/main/java/com/commafeed/frontend/CommaFeedSession.java @@ -9,7 +9,7 @@ import org.apache.wicket.request.Request; import com.commafeed.backend.dao.UserService; import com.commafeed.backend.model.User; -import com.commafeed.frontend.pages.auth.Role; +import com.commafeed.backend.security.Role; @SuppressWarnings("serial") public class CommaFeedSession extends AuthenticatedWebSession { diff --git a/src/main/java/com/commafeed/frontend/pages/HomePage.java b/src/main/java/com/commafeed/frontend/pages/HomePage.java index 08728349..7ff3ae36 100644 --- a/src/main/java/com/commafeed/frontend/pages/HomePage.java +++ b/src/main/java/com/commafeed/frontend/pages/HomePage.java @@ -5,7 +5,7 @@ import org.apache.wicket.markup.head.CssHeaderItem; import org.apache.wicket.markup.head.IHeaderResponse; import org.apache.wicket.markup.head.JavaScriptHeaderItem; -import com.commafeed.frontend.pages.auth.Role; +import com.commafeed.backend.security.Role; import com.commafeed.frontend.references.angular.AngularReference; import com.commafeed.frontend.references.angular.AngularResourceReference; import com.commafeed.frontend.references.angular.AngularSanitizeReference; diff --git a/src/main/java/com/commafeed/frontend/rest/JSONMessageBodyReaderWriter.java b/src/main/java/com/commafeed/frontend/rest/JSONMessageBodyReaderWriter.java index e8ce4d90..13427b3c 100644 --- a/src/main/java/com/commafeed/frontend/rest/JSONMessageBodyReaderWriter.java +++ b/src/main/java/com/commafeed/frontend/rest/JSONMessageBodyReaderWriter.java @@ -32,7 +32,7 @@ import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; @Provider -@Produces(MediaType.APPLICATION_JSON) +@Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN }) @Consumes(MediaType.APPLICATION_JSON) public class JSONMessageBodyReaderWriter implements MessageBodyWriter, MessageBodyReader { diff --git a/src/main/java/com/commafeed/frontend/rest/RESTApplication.java b/src/main/java/com/commafeed/frontend/rest/RESTApplication.java index 72bfafc9..d3b6d66a 100644 --- a/src/main/java/com/commafeed/frontend/rest/RESTApplication.java +++ b/src/main/java/com/commafeed/frontend/rest/RESTApplication.java @@ -1,27 +1,9 @@ package com.commafeed.frontend.rest; -import java.util.Set; - import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; -import com.commafeed.frontend.rest.resources.EntriesREST; -import com.commafeed.frontend.rest.resources.SettingsREST; -import com.commafeed.frontend.rest.resources.SubscriptionsREST; -import com.google.common.collect.Sets; - @ApplicationPath("/rest") public class RESTApplication extends Application { - @Override - public Set> getClasses() { - Set> set = Sets.newHashSet(); - set.add(JSONMessageBodyReaderWriter.class); - - set.add(SubscriptionsREST.class); - set.add(EntriesREST.class); - set.add(SettingsREST.class); - - return set; - } } diff --git a/src/main/java/com/commafeed/frontend/rest/SecurityCheck.java b/src/main/java/com/commafeed/frontend/rest/SecurityCheck.java new file mode 100644 index 00000000..a862944f --- /dev/null +++ b/src/main/java/com/commafeed/frontend/rest/SecurityCheck.java @@ -0,0 +1,23 @@ +package com.commafeed.frontend.rest; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.enterprise.util.Nonbinding; +import javax.interceptor.InterceptorBinding; + +@Inherited +@InterceptorBinding +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface SecurityCheck { + + /** + * Roles needed. + */ + @Nonbinding + String[] value() default {}; +} \ No newline at end of file diff --git a/src/main/java/com/commafeed/frontend/rest/resources/AbstractREST.java b/src/main/java/com/commafeed/frontend/rest/resources/AbstractREST.java index 37a6b745..025a4c97 100644 --- a/src/main/java/com/commafeed/frontend/rest/resources/AbstractREST.java +++ b/src/main/java/com/commafeed/frontend/rest/resources/AbstractREST.java @@ -1,7 +1,12 @@ package com.commafeed.frontend.rest.resources; +import java.lang.reflect.Method; +import java.util.List; + import javax.annotation.PostConstruct; import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.InvocationContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.Consumes; @@ -9,7 +14,7 @@ import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; import org.apache.wicket.ThreadContext; import org.apache.wicket.authentication.IAuthenticationStrategy; @@ -22,15 +27,19 @@ import com.commafeed.backend.dao.FeedEntryService; import com.commafeed.backend.dao.FeedEntryStatusService; import com.commafeed.backend.dao.FeedService; import com.commafeed.backend.dao.FeedSubscriptionService; +import com.commafeed.backend.dao.UserRoleService; import com.commafeed.backend.dao.UserService; import com.commafeed.backend.dao.UserSettingsService; import com.commafeed.backend.feeds.OPMLImporter; import com.commafeed.backend.model.User; +import com.commafeed.backend.security.Role; import com.commafeed.frontend.CommaFeedApplication; import com.commafeed.frontend.CommaFeedSession; +import com.commafeed.frontend.rest.SecurityCheck; @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) +@SecurityCheck(Role.USER) public abstract class AbstractREST { @Context @@ -60,6 +69,9 @@ public abstract class AbstractREST { @Inject UserSettingsService userSettingsService; + @Inject + UserRoleService userRoleService; + @Inject OPMLImporter opmlImporter; @@ -80,14 +92,46 @@ public abstract class AbstractREST { session.signIn(data[0], data[1]); } - if (getUser() == null) { - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - } protected User getUser() { return CommaFeedSession.get().getUser(); } + @AroundInvoke + public Object checkSecurity(InvocationContext context) throws Exception { + User user = getUser(); + if (user == null) { + throw new WebApplicationException(Status.UNAUTHORIZED); + } + + boolean allowed = false; + Method method = context.getMethod(); + + if (method.isAnnotationPresent(SecurityCheck.class)) { + allowed = checkRole(user, method.getAnnotation(SecurityCheck.class)); + } else if (method.getDeclaringClass().isAnnotationPresent( + SecurityCheck.class)) { + allowed = checkRole( + user, + method.getDeclaringClass().getAnnotation( + SecurityCheck.class)); + } + if (!allowed) { + throw new WebApplicationException(Status.FORBIDDEN); + } + + return context.proceed(); + } + + private boolean checkRole(User user, SecurityCheck annotation) { + List roles = userRoleService.getRoles(user); + for (String role : annotation.value()) { + if (!roles.contains(role)) { + return false; + } + } + return true; + } + } diff --git a/src/main/webapp/WEB-INF/beans.xml b/src/main/webapp/WEB-INF/beans.xml index 70d8902d..cb8aa009 100644 --- a/src/main/webapp/WEB-INF/beans.xml +++ b/src/main/webapp/WEB-INF/beans.xml @@ -1,11 +1,7 @@ - - - \ No newline at end of file