diff --git a/config.yml b/config.yml index 316e53fa..17d4227a 100644 --- a/config.yml +++ b/config.yml @@ -1,5 +1,5 @@ app: - publicUrl: http://localhost:8083/ + publicUrl: http://localhost:8082/ allowRegistrations: false googleAnalyticsTrackingCode: googleClientId: @@ -38,8 +38,10 @@ database: server: applicationConnectors: - type: http - port: 8082 - + port: 8083 + adminConnectors: + - type: http + port: 8084 logging: level: WARN loggers: diff --git a/gulpfile.js b/gulpfile.js index 78b93807..6d9c5102 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -63,9 +63,9 @@ gulp.task('watch', function() { gulp.task('serve', function() { connect.server({ root : BUILD_DIR, - port : 8083, + port : 8082, middleware : function() { - return [modRewrite(['^/rest/(.*)$ http://localhost:8082/rest/$1 [P]'])]; + return [modRewrite(['^/rest/(.*)$ http://localhost:8083/rest/$1 [P]'])]; } }); }); diff --git a/pom.xml b/pom.xml index 45f4b8bc..5a019456 100644 --- a/pom.xml +++ b/pom.xml @@ -137,11 +137,6 @@ dropwizard-core ${dropwizard.version} - - io.dropwizard - dropwizard-auth - ${dropwizard.version} - io.dropwizard dropwizard-hibernate diff --git a/src/main/java/com/commafeed/CommaFeedApplication.java b/src/main/java/com/commafeed/CommaFeedApplication.java index b4a965a6..68d40046 100644 --- a/src/main/java/com/commafeed/CommaFeedApplication.java +++ b/src/main/java/com/commafeed/CommaFeedApplication.java @@ -2,9 +2,6 @@ package com.commafeed; import io.dropwizard.Application; import io.dropwizard.assets.AssetsBundle; -import io.dropwizard.auth.CachingAuthenticator; -import io.dropwizard.auth.basic.BasicAuthProvider; -import io.dropwizard.auth.basic.BasicCredentials; import io.dropwizard.db.DataSourceFactory; import io.dropwizard.hibernate.HibernateBundle; import io.dropwizard.migrations.MigrationsBundle; @@ -61,6 +58,7 @@ import com.commafeed.backend.service.PasswordEncryptionService; import com.commafeed.backend.service.PubSubService; import com.commafeed.backend.service.StartupService; import com.commafeed.backend.service.UserService; +import com.commafeed.frontend.auth.SecurityCheckProvider; import com.commafeed.frontend.resource.AdminREST; import com.commafeed.frontend.resource.CategoryREST; import com.commafeed.frontend.resource.EntryREST; @@ -152,9 +150,8 @@ public class CommaFeedApplication extends Application { FeedRefreshWorker feedWorker = new FeedRefreshWorker(feedUpdater, feedFetcher, queues, config, metrics); FeedRefreshTaskGiver taskGiver = new FeedRefreshTaskGiver(sessionFactory, queues, feedDAO, feedWorker, config, metrics); - CachingAuthenticator cachingAuthenticator = new CachingAuthenticator( - environment.metrics(), new CommaFeedAuthenticator(userService), config.getAuthenticationCachePolicy()); - environment.jersey().register(new BasicAuthProvider(cachingAuthenticator, "CommaFeed")); + // TODO add caching of credentials somehow + environment.jersey().register(new SecurityCheckProvider(userService)); environment.jersey().setUrlPattern("/rest/*"); environment.jersey() diff --git a/src/main/java/com/commafeed/CommaFeedAuthenticator.java b/src/main/java/com/commafeed/CommaFeedAuthenticator.java deleted file mode 100644 index 4c729e17..00000000 --- a/src/main/java/com/commafeed/CommaFeedAuthenticator.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.commafeed; - -import io.dropwizard.auth.AuthenticationException; -import io.dropwizard.auth.Authenticator; -import io.dropwizard.auth.basic.BasicCredentials; -import lombok.RequiredArgsConstructor; - -import com.commafeed.backend.model.User; -import com.commafeed.backend.service.UserService; -import com.google.common.base.Optional; - -@RequiredArgsConstructor -public class CommaFeedAuthenticator implements Authenticator { - - private final UserService userService; - - @Override - public Optional authenticate(final BasicCredentials credentials) throws AuthenticationException { - return Optional.fromNullable(userService.login(credentials.getUsername(), credentials.getPassword())); - } -} diff --git a/src/main/java/com/commafeed/backend/dao/UserDAO.java b/src/main/java/com/commafeed/backend/dao/UserDAO.java index a9ab20c5..14bdbf01 100644 --- a/src/main/java/com/commafeed/backend/dao/UserDAO.java +++ b/src/main/java/com/commafeed/backend/dao/UserDAO.java @@ -3,6 +3,7 @@ package com.commafeed.backend.dao; import org.hibernate.SessionFactory; import com.commafeed.backend.model.QUser; +import com.commafeed.backend.model.QUserRole; import com.commafeed.backend.model.User; public class UserDAO extends GenericDAO { @@ -14,14 +15,17 @@ public class UserDAO extends GenericDAO { } public User findByName(String name) { - return newQuery().from(user).where(user.name.equalsIgnoreCase(name)).uniqueResult(user); + return newQuery().from(user).where(user.name.equalsIgnoreCase(name)).leftJoin(user.roles, QUserRole.userRole).fetch() + .uniqueResult(user); } public User findByApiKey(String key) { - return newQuery().from(user).where(user.apiKey.equalsIgnoreCase(key)).uniqueResult(user); + return newQuery().from(user).where(user.apiKey.equalsIgnoreCase(key)).leftJoin(user.roles, QUserRole.userRole).fetch() + .uniqueResult(user); } public User findByEmail(String email) { - return newQuery().from(user).where(user.email.equalsIgnoreCase(email)).uniqueResult(user); + return newQuery().from(user).where(user.email.equalsIgnoreCase(email)).leftJoin(user.roles, QUserRole.userRole).fetch() + .uniqueResult(user); } } diff --git a/src/main/java/com/commafeed/backend/model/User.java b/src/main/java/com/commafeed/backend/model/User.java index 1b69d57e..e1fe1394 100644 --- a/src/main/java/com/commafeed/backend/model/User.java +++ b/src/main/java/com/commafeed/backend/model/User.java @@ -17,6 +17,7 @@ import lombok.Setter; import org.hibernate.annotations.Cascade; +import com.commafeed.backend.model.UserRole.Role; import com.google.common.collect.Sets; @Entity @@ -68,4 +69,13 @@ public class User extends AbstractModel { @Temporal(TemporalType.TIMESTAMP) private Date lastFullRefresh; + public boolean hasRole(Role role) { + for (UserRole userRole : getRoles()) { + if (userRole.getRole() == role) { + return true; + } + } + return false; + } + } diff --git a/src/main/java/com/commafeed/backend/model/UserRole.java b/src/main/java/com/commafeed/backend/model/UserRole.java index bed925d5..a32d4e63 100644 --- a/src/main/java/com/commafeed/backend/model/UserRole.java +++ b/src/main/java/com/commafeed/backend/model/UserRole.java @@ -20,7 +20,7 @@ import lombok.Setter; public class UserRole extends AbstractModel { public static enum Role { - USER, ADMIN, NONE + USER, ADMIN } @OneToOne(fetch = FetchType.LAZY) diff --git a/src/main/java/com/commafeed/backend/service/UserService.java b/src/main/java/com/commafeed/backend/service/UserService.java index 14e2aa6b..e71592a8 100644 --- a/src/main/java/com/commafeed/backend/service/UserService.java +++ b/src/main/java/com/commafeed/backend/service/UserService.java @@ -64,6 +64,18 @@ public class UserService { return null; } + public User login(String apiKey) { + if (apiKey == null) { + return null; + } + + User user = userDAO.findByApiKey(apiKey); + if (user != null && !user.isDisabled()) { + return user; + } + return null; + } + public User register(String name, String password, String email, Collection roles) { return register(name, password, email, roles, false); } diff --git a/src/main/java/com/commafeed/frontend/auth/SecurityCheck.java b/src/main/java/com/commafeed/frontend/auth/SecurityCheck.java new file mode 100644 index 00000000..0e685686 --- /dev/null +++ b/src/main/java/com/commafeed/frontend/auth/SecurityCheck.java @@ -0,0 +1,22 @@ +package com.commafeed.frontend.auth; + +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 com.commafeed.backend.model.UserRole.Role; + +@Inherited +@Target({ ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface SecurityCheck { + + /** + * Roles needed. + */ + Role value() default Role.USER; + + boolean apiKeyAllowed() default false; +} \ No newline at end of file diff --git a/src/main/java/com/commafeed/frontend/auth/SecurityCheckProvider.java b/src/main/java/com/commafeed/frontend/auth/SecurityCheckProvider.java new file mode 100644 index 00000000..47402e27 --- /dev/null +++ b/src/main/java/com/commafeed/frontend/auth/SecurityCheckProvider.java @@ -0,0 +1,95 @@ +package com.commafeed.frontend.auth; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import lombok.extern.slf4j.Slf4j; + +import org.eclipse.jetty.util.B64Code; +import org.eclipse.jetty.util.StringUtil; + +import com.commafeed.backend.model.User; +import com.commafeed.backend.model.UserRole.Role; +import com.commafeed.backend.service.UserService; +import com.sun.jersey.api.core.HttpContext; +import com.sun.jersey.api.model.Parameter; +import com.sun.jersey.core.spi.component.ComponentContext; +import com.sun.jersey.core.spi.component.ComponentScope; +import com.sun.jersey.server.impl.inject.AbstractHttpContextInjectable; +import com.sun.jersey.spi.inject.Injectable; +import com.sun.jersey.spi.inject.InjectableProvider; + +@Slf4j +public class SecurityCheckProvider implements InjectableProvider { + + private static class SecurityCheckInjectable extends AbstractHttpContextInjectable { + private static final String PREFIX = "Basic"; + + private final UserService userService; + private Role role; + private final boolean apiKeyAllowed; + + private SecurityCheckInjectable(UserService userService, Role role, boolean apiKeyAllowed) { + this.userService = userService; + this.role = role; + this.apiKeyAllowed = apiKeyAllowed; + } + + @Override + public User getValue(HttpContext c) { + final String header = c.getRequest().getHeaderValue(HttpHeaders.AUTHORIZATION); + try { + if (header != null) { + final int space = header.indexOf(' '); + if (space > 0) { + final String method = header.substring(0, space); + if (PREFIX.equalsIgnoreCase(method)) { + final String decoded = B64Code.decode(header.substring(space + 1), StringUtil.__ISO_8859_1); + final int i = decoded.indexOf(':'); + if (i > 0) { + final String username = decoded.substring(0, i); + final String password = decoded.substring(i + 1); + final User user = userService.login(username, password); + if (user != null && user.hasRole(role)) { + return user; + } + } + } + } + } else { + String apiKey = c.getUriInfo().getPathParameters().getFirst("apiKey"); + if (apiKey != null && apiKeyAllowed) { + User user = userService.login(apiKey); + if (user != null && user.hasRole(role)) { + return user; + } + } + } + } catch (IllegalArgumentException e) { + log.debug("Error decoding credentials", e); + } + + throw new WebApplicationException(Response.status(Response.Status.UNAUTHORIZED) + .header(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"CommaFeed\"") + .entity("Credentials are required to access this resource.").type(MediaType.TEXT_PLAIN_TYPE).build()); + } + } + + private UserService userService; + + public SecurityCheckProvider(UserService userService) { + this.userService = userService; + } + + @Override + public ComponentScope getScope() { + return ComponentScope.PerRequest; + } + + @Override + public Injectable getInjectable(ComponentContext ic, SecurityCheck sc, Parameter c) { + return new SecurityCheckInjectable<>(userService, sc.value(), sc.apiKeyAllowed()); + } +} diff --git a/src/main/java/com/commafeed/frontend/resource/AdminREST.java b/src/main/java/com/commafeed/frontend/resource/AdminREST.java index aaec677d..8711168e 100644 --- a/src/main/java/com/commafeed/frontend/resource/AdminREST.java +++ b/src/main/java/com/commafeed/frontend/resource/AdminREST.java @@ -1,6 +1,5 @@ package com.commafeed.frontend.resource; -import io.dropwizard.auth.Auth; import io.dropwizard.hibernate.UnitOfWork; import java.util.Map; @@ -32,6 +31,7 @@ import com.commafeed.backend.model.UserRole.Role; import com.commafeed.backend.service.DatabaseCleaningService; import com.commafeed.backend.service.PasswordEncryptionService; import com.commafeed.backend.service.UserService; +import com.commafeed.frontend.auth.SecurityCheck; import com.commafeed.frontend.model.UserModel; import com.commafeed.frontend.model.request.IDRequest; import com.google.common.base.Preconditions; @@ -62,7 +62,7 @@ public class AdminREST { @POST @UnitOfWork @ApiOperation(value = "Save or update a user", notes = "Save or update a user. If the id is not specified, a new user will be created") - public Response save(@Auth User user, @ApiParam(required = true) UserModel userModel) { + public Response save(@SecurityCheck(Role.ADMIN) User user, @ApiParam(required = true) UserModel userModel) { Preconditions.checkNotNull(userModel); Preconditions.checkNotNull(userModel.getName()); @@ -115,7 +115,7 @@ public class AdminREST { @GET @UnitOfWork @ApiOperation(value = "Get user information", notes = "Get user information", response = UserModel.class) - public Response getUser(@Auth User user, @ApiParam(value = "user id", required = true) @PathParam("id") Long id) { + public Response getUser(@SecurityCheck(Role.ADMIN) User user, @ApiParam(value = "user id", required = true) @PathParam("id") Long id) { Preconditions.checkNotNull(id); User u = userDAO.findById(id); UserModel userModel = new UserModel(); @@ -135,7 +135,7 @@ public class AdminREST { @GET @UnitOfWork @ApiOperation(value = "Get all users", notes = "Get all users", response = UserModel.class, responseContainer = "List") - public Response getUsers(@Auth User user) { + public Response getUsers(@SecurityCheck(Role.ADMIN) User user) { Map users = Maps.newHashMap(); for (UserRole role : userRoleDAO.findAll()) { User u = role.getUser(); @@ -162,7 +162,7 @@ public class AdminREST { @POST @UnitOfWork @ApiOperation(value = "Delete a user", notes = "Delete a user, and all his subscriptions") - public Response delete(@Auth User user, @ApiParam(required = true) IDRequest req) { + public Response delete(@SecurityCheck(Role.ADMIN) User user, @ApiParam(required = true) IDRequest req) { Preconditions.checkNotNull(req); Preconditions.checkNotNull(req.getId()); @@ -181,7 +181,7 @@ public class AdminREST { @GET @UnitOfWork @ApiOperation(value = "Retrieve application settings", notes = "Retrieve application settings", response = ApplicationSettings.class) - public Response getSettings(@Auth User user) { + public Response getSettings(@SecurityCheck(Role.ADMIN) User user) { return Response.ok(config.getApplicationSettings()).build(); } @@ -189,7 +189,7 @@ public class AdminREST { @GET @UnitOfWork @ApiOperation(value = "Retrieve server metrics") - public Response getMetrics(@Auth User user) { + public Response getMetrics(@SecurityCheck(Role.ADMIN) User user) { return Response.ok(metrics).build(); } @@ -197,7 +197,7 @@ public class AdminREST { @GET @UnitOfWork @ApiOperation(value = "Entries cleanup", notes = "Delete entries without subscriptions") - public Response cleanupEntries(@Auth User user) { + public Response cleanupEntries(@SecurityCheck(Role.ADMIN) User user) { Map map = Maps.newHashMap(); map.put("entries_without_subscriptions", cleaner.cleanEntriesWithoutSubscriptions()); return Response.ok(map).build(); @@ -207,7 +207,7 @@ public class AdminREST { @GET @UnitOfWork @ApiOperation(value = "Feeds cleanup", notes = "Delete feeds without subscriptions") - public Response cleanupFeeds(@Auth User user) { + public Response cleanupFeeds(@SecurityCheck(Role.ADMIN) User user) { Map map = Maps.newHashMap(); map.put("feeds_without_subscriptions", cleaner.cleanFeedsWithoutSubscriptions()); return Response.ok(map).build(); @@ -217,7 +217,7 @@ public class AdminREST { @GET @UnitOfWork @ApiOperation(value = "Content cleanup", notes = "Delete contents without entries") - public Response cleanupContents(@Auth User user) { + public Response cleanupContents(@SecurityCheck(Role.ADMIN) User user) { Map map = Maps.newHashMap(); map.put("contents_without_entries", cleaner.cleanContentsWithoutEntries()); return Response.ok(map).build(); diff --git a/src/main/java/com/commafeed/frontend/resource/CategoryREST.java b/src/main/java/com/commafeed/frontend/resource/CategoryREST.java index ad1f218f..1819bf60 100644 --- a/src/main/java/com/commafeed/frontend/resource/CategoryREST.java +++ b/src/main/java/com/commafeed/frontend/resource/CategoryREST.java @@ -1,6 +1,5 @@ package com.commafeed.frontend.resource; -import io.dropwizard.auth.Auth; import io.dropwizard.hibernate.UnitOfWork; import java.io.StringWriter; @@ -43,6 +42,7 @@ import com.commafeed.backend.model.UserSettings.ReadingMode; import com.commafeed.backend.model.UserSettings.ReadingOrder; import com.commafeed.backend.service.FeedEntryService; import com.commafeed.backend.service.FeedSubscriptionService; +import com.commafeed.frontend.auth.SecurityCheck; import com.commafeed.frontend.model.Category; import com.commafeed.frontend.model.Entries; import com.commafeed.frontend.model.Entry; @@ -88,7 +88,7 @@ public class CategoryREST { @UnitOfWork @ApiOperation(value = "Get category entries", notes = "Get a list of category entries", response = Entries.class) public Response getCategoryEntries( - @Auth User user, + @SecurityCheck User user, @ApiParam(value = "id of the category, 'all' or 'starred'", required = true) @QueryParam("id") String id, @ApiParam(value = "all entries or only unread ones", allowableValues = "all,unread", required = true) @DefaultValue("unread") @QueryParam("readType") ReadingMode readType, @ApiParam(value = "only entries newer than this") @QueryParam("newerThan") Long newerThan, @@ -186,7 +186,7 @@ public class CategoryREST { @ApiOperation(value = "Get category entries as feed", notes = "Get a feed of category entries") @Produces(MediaType.APPLICATION_XML) public Response getCategoryEntriesAsFeed( - @Auth User user, + @SecurityCheck(apiKeyAllowed = true) User user, @ApiParam(value = "id of the category, 'all' or 'starred'", required = true) @QueryParam("id") String id, @ApiParam(value = "all entries or only unread ones", allowableValues = "all,unread", required = true) @DefaultValue("all") @QueryParam("readType") ReadingMode readType, @ApiParam(value = "only entries newer than this") @QueryParam("newerThan") Long newerThan, @@ -234,7 +234,8 @@ public class CategoryREST { @POST @UnitOfWork @ApiOperation(value = "Mark category entries", notes = "Mark feed entries of this category as read") - public Response markCategoryEntries(@Auth User user, @ApiParam(value = "category id, or 'all'", required = true) MarkRequest req) { + public Response markCategoryEntries(@SecurityCheck User user, + @ApiParam(value = "category id, or 'all'", required = true) MarkRequest req) { Preconditions.checkNotNull(req); Preconditions.checkNotNull(req.getId()); @@ -272,7 +273,7 @@ public class CategoryREST { @POST @UnitOfWork @ApiOperation(value = "Add a category", notes = "Add a new feed category", response = Long.class) - public Response addCategory(@Auth User user, @ApiParam(required = true) AddCategoryRequest req) { + public Response addCategory(@SecurityCheck User user, @ApiParam(required = true) AddCategoryRequest req) { Preconditions.checkNotNull(req); Preconditions.checkNotNull(req.getName()); @@ -295,7 +296,7 @@ public class CategoryREST { @Path("/delete") @UnitOfWork @ApiOperation(value = "Delete a category", notes = "Delete an existing feed category") - public Response deleteCategory(@Auth User user, @ApiParam(required = true) IDRequest req) { + public Response deleteCategory(@SecurityCheck User user, @ApiParam(required = true) IDRequest req) { Preconditions.checkNotNull(req); Preconditions.checkNotNull(req.getId()); @@ -327,7 +328,7 @@ public class CategoryREST { @Path("/modify") @UnitOfWork @ApiOperation(value = "Rename a category", notes = "Rename an existing feed category") - public Response modifyCategory(@Auth User user, @ApiParam(required = true) CategoryModificationRequest req) { + public Response modifyCategory(@SecurityCheck User user, @ApiParam(required = true) CategoryModificationRequest req) { Preconditions.checkNotNull(req); Preconditions.checkNotNull(req.getId()); @@ -381,7 +382,7 @@ public class CategoryREST { @Path("/collapse") @UnitOfWork @ApiOperation(value = "Collapse a category", notes = "Save collapsed or expanded status for a category") - public Response collapse(@Auth User user, @ApiParam(required = true) CollapseRequest req) { + public Response collapse(@SecurityCheck User user, @ApiParam(required = true) CollapseRequest req) { Preconditions.checkNotNull(req); Preconditions.checkNotNull(req.getId()); @@ -399,7 +400,7 @@ public class CategoryREST { @Path("/unreadCount") @UnitOfWork @ApiOperation(value = "Get unread count for feed subscriptions", response = UnreadCount.class, responseContainer = "List") - public Response getUnreadCount(@Auth User user) { + public Response getUnreadCount(@SecurityCheck User user) { Map unreadCount = feedSubscriptionService.getUnreadCount(user); return Response.ok(Lists.newArrayList(unreadCount.values())).build(); } @@ -408,7 +409,7 @@ public class CategoryREST { @Path("/get") @UnitOfWork @ApiOperation(value = "Get feed categories", notes = "Get all categories and subscriptions of the user", response = Category.class) - public Response getSubscriptions(@Auth User user) { + public Response getSubscriptions(@SecurityCheck User user) { Category root = cache.getUserRootCategory(user); if (root == null) { log.debug("tree cache miss for {}", user.getId()); diff --git a/src/main/java/com/commafeed/frontend/resource/EntryREST.java b/src/main/java/com/commafeed/frontend/resource/EntryREST.java index bef9b51a..d9c4771a 100644 --- a/src/main/java/com/commafeed/frontend/resource/EntryREST.java +++ b/src/main/java/com/commafeed/frontend/resource/EntryREST.java @@ -1,6 +1,5 @@ package com.commafeed.frontend.resource; -import io.dropwizard.auth.Auth; import io.dropwizard.hibernate.UnitOfWork; import java.util.List; @@ -19,6 +18,7 @@ import com.commafeed.backend.dao.FeedEntryTagDAO; import com.commafeed.backend.model.User; import com.commafeed.backend.service.FeedEntryService; import com.commafeed.backend.service.FeedEntryTagService; +import com.commafeed.frontend.auth.SecurityCheck; import com.commafeed.frontend.model.request.MarkRequest; import com.commafeed.frontend.model.request.MultipleMarkRequest; import com.commafeed.frontend.model.request.StarRequest; @@ -43,7 +43,7 @@ public class EntryREST { @POST @UnitOfWork @ApiOperation(value = "Mark a feed entry", notes = "Mark a feed entry as read/unread") - public Response markFeedEntry(@Auth User user, @ApiParam(value = "Mark Request", required = true) MarkRequest req) { + public Response markFeedEntry(@SecurityCheck User user, @ApiParam(value = "Mark Request", required = true) MarkRequest req) { Preconditions.checkNotNull(req); Preconditions.checkNotNull(req.getId()); @@ -55,7 +55,8 @@ public class EntryREST { @POST @UnitOfWork @ApiOperation(value = "Mark multiple feed entries", notes = "Mark feed entries as read/unread") - public Response markFeedEntries(@Auth User user, @ApiParam(value = "Multiple Mark Request", required = true) MultipleMarkRequest req) { + public Response markFeedEntries(@SecurityCheck User user, + @ApiParam(value = "Multiple Mark Request", required = true) MultipleMarkRequest req) { Preconditions.checkNotNull(req); Preconditions.checkNotNull(req.getRequests()); @@ -70,7 +71,7 @@ public class EntryREST { @POST @UnitOfWork @ApiOperation(value = "Mark a feed entry", notes = "Mark a feed entry as read/unread") - public Response starFeedEntry(@Auth User user, @ApiParam(value = "Star Request", required = true) StarRequest req) { + public Response starFeedEntry(@SecurityCheck User user, @ApiParam(value = "Star Request", required = true) StarRequest req) { Preconditions.checkNotNull(req); Preconditions.checkNotNull(req.getId()); Preconditions.checkNotNull(req.getFeedId()); @@ -84,7 +85,7 @@ public class EntryREST { @GET @UnitOfWork @ApiOperation(value = "Get list of tags for the user", notes = "Get list of tags for the user") - public Response getTags(@Auth User user) { + public Response getTags(@SecurityCheck User user) { List tags = feedEntryTagDAO.findByUser(user); return Response.ok(tags).build(); } @@ -93,7 +94,7 @@ public class EntryREST { @POST @UnitOfWork @ApiOperation(value = "Mark a feed entry", notes = "Mark a feed entry as read/unread") - public Response tagFeedEntry(@Auth User user, @ApiParam(value = "Tag Request", required = true) TagRequest req) { + public Response tagFeedEntry(@SecurityCheck User user, @ApiParam(value = "Tag Request", required = true) TagRequest req) { Preconditions.checkNotNull(req); Preconditions.checkNotNull(req.getEntryId()); diff --git a/src/main/java/com/commafeed/frontend/resource/FeedREST.java b/src/main/java/com/commafeed/frontend/resource/FeedREST.java index b36f5f71..ef56bbd5 100644 --- a/src/main/java/com/commafeed/frontend/resource/FeedREST.java +++ b/src/main/java/com/commafeed/frontend/resource/FeedREST.java @@ -1,6 +1,5 @@ package com.commafeed.frontend.resource; -import io.dropwizard.auth.Auth; import io.dropwizard.hibernate.UnitOfWork; import java.io.InputStream; @@ -57,6 +56,7 @@ import com.commafeed.backend.opml.OPMLExporter; import com.commafeed.backend.opml.OPMLImporter; import com.commafeed.backend.service.FeedEntryService; import com.commafeed.backend.service.FeedSubscriptionService; +import com.commafeed.frontend.auth.SecurityCheck; import com.commafeed.frontend.model.Entries; import com.commafeed.frontend.model.Entry; import com.commafeed.frontend.model.FeedInfo; @@ -107,7 +107,7 @@ public class FeedREST { @UnitOfWork @ApiOperation(value = "Get feed entries", notes = "Get a list of feed entries", response = Entries.class) public Response getFeedEntries( - @Auth User user, + @SecurityCheck User user, @ApiParam(value = "id of the feed", required = true) @QueryParam("id") String id, @ApiParam(value = "all entries or only unread ones", allowableValues = "all,unread", required = true) @DefaultValue("unread") @QueryParam("readType") ReadingMode readType, @ApiParam(value = "only entries newer than this") @QueryParam("newerThan") Long newerThan, @@ -172,7 +172,7 @@ public class FeedREST { @ApiOperation(value = "Get feed entries as a feed", notes = "Get a feed of feed entries") @Produces(MediaType.APPLICATION_XML) public Response getFeedEntriesAsFeed( - @Auth User user, + @SecurityCheck(apiKeyAllowed = true) User user, @ApiParam(value = "id of the feed", required = true) @QueryParam("id") String id, @ApiParam(value = "all entries or only unread ones", allowableValues = "all,unread", required = true) @DefaultValue("all") @QueryParam("readType") ReadingMode readType, @ApiParam(value = "only entries newer than this") @QueryParam("newerThan") Long newerThan, @@ -234,7 +234,7 @@ public class FeedREST { @Path("/fetch") @UnitOfWork @ApiOperation(value = "Fetch a feed", notes = "Fetch a feed by its url", response = FeedInfo.class) - public Response fetchFeed(@Auth User user, @ApiParam(value = "feed url", required = true) FeedInfoRequest req) { + public Response fetchFeed(@SecurityCheck User user, @ApiParam(value = "feed url", required = true) FeedInfoRequest req) { Preconditions.checkNotNull(req); Preconditions.checkNotNull(req.getUrl()); @@ -252,7 +252,7 @@ public class FeedREST { @GET @UnitOfWork @ApiOperation(value = "Queue all feeds of the user for refresh", notes = "Manually add all feeds of the user to the refresh queue") - public Response queueAllForRefresh(@Auth User user) { + public Response queueAllForRefresh(@SecurityCheck User user) { feedSubscriptionService.refreshAll(user); return Response.ok().build(); } @@ -261,7 +261,7 @@ public class FeedREST { @POST @UnitOfWork @ApiOperation(value = "Queue a feed for refresh", notes = "Manually add a feed to the refresh queue") - public Response queueForRefresh(@Auth User user, @ApiParam(value = "Feed id") IDRequest req) { + public Response queueForRefresh(@SecurityCheck User user, @ApiParam(value = "Feed id") IDRequest req) { Preconditions.checkNotNull(req); Preconditions.checkNotNull(req.getId()); @@ -279,7 +279,7 @@ public class FeedREST { @POST @UnitOfWork @ApiOperation(value = "Mark feed entries", notes = "Mark feed entries as read (unread is not supported)") - public Response markFeedEntries(@Auth User user, @ApiParam(value = "Mark request") MarkRequest req) { + public Response markFeedEntries(@SecurityCheck User user, @ApiParam(value = "Mark request") MarkRequest req) { Preconditions.checkNotNull(req); Preconditions.checkNotNull(req.getId()); @@ -296,7 +296,7 @@ public class FeedREST { @Path("/get/{id}") @UnitOfWork @ApiOperation(value = "", notes = "") - public Response get(@Auth User user, @ApiParam(value = "user id", required = true) @PathParam("id") Long id) { + public Response get(@SecurityCheck User user, @ApiParam(value = "user id", required = true) @PathParam("id") Long id) { Preconditions.checkNotNull(id); FeedSubscription sub = feedSubscriptionDAO.findById(user, id); @@ -311,7 +311,7 @@ public class FeedREST { @Path("/favicon/{id}") @UnitOfWork @ApiOperation(value = "Fetch a feed's icon", notes = "Fetch a feed's icon") - public Response getFavicon(@Auth User user, @ApiParam(value = "subscription id") @PathParam("id") Long id) { + public Response getFavicon(@SecurityCheck User user, @ApiParam(value = "subscription id") @PathParam("id") Long id) { Preconditions.checkNotNull(id); FeedSubscription subscription = feedSubscriptionDAO.findById(user, id); @@ -348,7 +348,7 @@ public class FeedREST { @Path("/subscribe") @UnitOfWork @ApiOperation(value = "Subscribe to a feed", notes = "Subscribe to a feed") - public Response subscribe(@Auth User user, @ApiParam(value = "subscription request", required = true) SubscribeRequest req) { + public Response subscribe(@SecurityCheck User user, @ApiParam(value = "subscription request", required = true) SubscribeRequest req) { Preconditions.checkNotNull(req); Preconditions.checkNotNull(req.getTitle()); Preconditions.checkNotNull(req.getUrl()); @@ -374,7 +374,7 @@ public class FeedREST { @Path("/subscribe") @UnitOfWork @ApiOperation(value = "Subscribe to a feed", notes = "Subscribe to a feed") - public Response subscribe(@Auth User user, @ApiParam(value = "feed url", required = true) @QueryParam("url") String url) { + public Response subscribe(@SecurityCheck User user, @ApiParam(value = "feed url", required = true) @QueryParam("url") String url) { try { Preconditions.checkNotNull(url); @@ -401,7 +401,7 @@ public class FeedREST { @Path("/unsubscribe") @UnitOfWork @ApiOperation(value = "Unsubscribe from a feed", notes = "Unsubscribe from a feed") - public Response unsubscribe(@Auth User user, @ApiParam(required = true) IDRequest req) { + public Response unsubscribe(@SecurityCheck User user, @ApiParam(required = true) IDRequest req) { Preconditions.checkNotNull(req); Preconditions.checkNotNull(req.getId()); @@ -417,7 +417,7 @@ public class FeedREST { @Path("/modify") @UnitOfWork @ApiOperation(value = "Modify a subscription", notes = "Modify a feed subscription") - public Response modify(@Auth User user, @ApiParam(value = "subscription id", required = true) FeedModificationRequest req) { + public Response modify(@SecurityCheck User user, @ApiParam(value = "subscription id", required = true) FeedModificationRequest req) { Preconditions.checkNotNull(req); Preconditions.checkNotNull(req.getId()); @@ -469,7 +469,7 @@ public class FeedREST { @UnitOfWork @Consumes(MediaType.MULTIPART_FORM_DATA) @ApiOperation(value = "OPML import", notes = "Import an OPML file, posted as a FORM with the 'file' name") - public Response importOpml(@Auth User user, @FormDataParam("file") InputStream input) { + public Response importOpml(@SecurityCheck User user, @FormDataParam("file") InputStream input) { String publicUrl = config.getApplicationSettings().getPublicUrl(); if (StringUtils.isBlank(publicUrl)) { @@ -495,7 +495,7 @@ public class FeedREST { @UnitOfWork @Produces(MediaType.APPLICATION_XML) @ApiOperation(value = "OPML export", notes = "Export an OPML file of the user's subscriptions") - public Response exportOpml(@Auth User user) { + public Response exportOpml(@SecurityCheck User user) { Opml opml = opmlExporter.export(user); WireFeedOutput output = new WireFeedOutput(); String opmlString = null; diff --git a/src/main/java/com/commafeed/frontend/resource/ServerREST.java b/src/main/java/com/commafeed/frontend/resource/ServerREST.java index d16d2dc4..47310049 100644 --- a/src/main/java/com/commafeed/frontend/resource/ServerREST.java +++ b/src/main/java/com/commafeed/frontend/resource/ServerREST.java @@ -1,6 +1,5 @@ package com.commafeed.frontend.resource; -import io.dropwizard.auth.Auth; import io.dropwizard.hibernate.UnitOfWork; import javax.ws.rs.Consumes; @@ -20,6 +19,7 @@ import com.commafeed.backend.HttpGetter.HttpResult; import com.commafeed.backend.feed.FeedUtils; import com.commafeed.backend.model.User; import com.commafeed.backend.service.ApplicationPropertiesService; +import com.commafeed.frontend.auth.SecurityCheck; import com.commafeed.frontend.model.ServerInfo; import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.annotations.ApiOperation; @@ -39,7 +39,7 @@ public class ServerREST { @GET @UnitOfWork @ApiOperation(value = "Get server infos", notes = "Get server infos", response = ServerInfo.class) - public Response get(@Auth User user) { + public Response get(@SecurityCheck User user) { ServerInfo infos = new ServerInfo(); infos.setAnnouncement(config.getApplicationSettings().getAnnouncement()); infos.setVersion(applicationPropertiesService.getVersion()); @@ -52,7 +52,7 @@ public class ServerREST { @UnitOfWork @ApiOperation(value = "proxy image") @Produces("image/png") - public Response get(@Auth User user, @QueryParam("u") String url) { + public Response get(@SecurityCheck User user, @QueryParam("u") String url) { if (!config.getApplicationSettings().isImageProxyEnabled()) { return Response.status(Status.FORBIDDEN).build(); } diff --git a/src/main/java/com/commafeed/frontend/resource/UserREST.java b/src/main/java/com/commafeed/frontend/resource/UserREST.java index b8ff3bcf..eb22084d 100644 --- a/src/main/java/com/commafeed/frontend/resource/UserREST.java +++ b/src/main/java/com/commafeed/frontend/resource/UserREST.java @@ -1,6 +1,5 @@ package com.commafeed.frontend.resource; -import io.dropwizard.auth.Auth; import io.dropwizard.hibernate.UnitOfWork; import java.util.Arrays; @@ -31,6 +30,7 @@ import com.commafeed.backend.model.UserSettings.ReadingOrder; import com.commafeed.backend.model.UserSettings.ViewMode; import com.commafeed.backend.service.PasswordEncryptionService; import com.commafeed.backend.service.UserService; +import com.commafeed.frontend.auth.SecurityCheck; import com.commafeed.frontend.model.Settings; import com.commafeed.frontend.model.UserModel; import com.commafeed.frontend.model.request.ProfileModificationRequest; @@ -57,7 +57,7 @@ public class UserREST { @GET @UnitOfWork @ApiOperation(value = "Retrieve user settings", notes = "Retrieve user settings", response = Settings.class) - public Response getSettings(@Auth User user) { + public Response getSettings(@SecurityCheck User user) { Settings s = new Settings(); UserSettings settings = userSettingsDAO.findByUser(user); if (settings != null) { @@ -111,7 +111,7 @@ public class UserREST { @POST @UnitOfWork @ApiOperation(value = "Save user settings", notes = "Save user settings") - public Response saveSettings(@Auth User user, @ApiParam(required = true) Settings settings) { + public Response saveSettings(@SecurityCheck User user, @ApiParam(required = true) Settings settings) { Preconditions.checkNotNull(settings); UserSettings s = userSettingsDAO.findByUser(user); @@ -149,7 +149,7 @@ public class UserREST { @GET @UnitOfWork @ApiOperation(value = "Retrieve user's profile", response = UserModel.class) - public Response get(@Auth User user) { + public Response get(@SecurityCheck User user) { UserModel userModel = new UserModel(); userModel.setId(user.getId()); userModel.setName(user.getName()); @@ -168,7 +168,7 @@ public class UserREST { @POST @UnitOfWork @ApiOperation(value = "Save user's profile") - public Response save(@Auth User user, @ApiParam(required = true) ProfileModificationRequest request) { + public Response save(@SecurityCheck User user, @ApiParam(required = true) ProfileModificationRequest request) { Preconditions.checkArgument(StringUtils.isBlank(request.getPassword()) || request.getPassword().length() >= 6); if (StringUtils.isNotBlank(request.getEmail())) { User u = userDAO.findByEmail(request.getEmail()); @@ -210,7 +210,7 @@ public class UserREST { @POST @UnitOfWork @ApiOperation(value = "Delete the user account") - public Response delete(@Auth User user) { + public Response delete(@SecurityCheck User user) { if (CommaFeedApplication.USERNAME_ADMIN.equals(user.getName()) || CommaFeedApplication.USERNAME_DEMO.equals(user.getName())) { return Response.status(Status.FORBIDDEN).build(); }