Skip to content

Commit

Permalink
Several changes:
Browse files Browse the repository at this point in the history
- Added endpoint for a first version of the home inbox (GET /notification/user/{uuid})
- Added endpoints for getting user's assemblies, groups and ongoing campaigns
- Added endpoints for getting user's profile
- Updated access control rules
- Fixed issues with authentication (logout redirect null exception)
- Fixed and extended assemblies list endpoints
- Added basic assembly search query
- Extended Swagger documentation with more annotations so that the doc can be used for testing purposes
  • Loading branch information
cdparra committed Sep 13, 2015
1 parent bd9ba41 commit 9811ffc
Show file tree
Hide file tree
Showing 43 changed files with 1,524 additions and 234 deletions.
2 changes: 1 addition & 1 deletion Procfile
Original file line number Diff line number Diff line change
@@ -1 +1 @@
web: target/universal/stage/bin/appcivist-core -Dhttp.port=${PORT} -Ddb.default.driver=org.postgresql.Driver -Ddb.default.url=${DATABASE_URL} -Dconfig.resource=heroku.conf
web: target/universal/stage/bin/appcivist-core -Dhttp.port=${PORT} -Ddb.default.driver=org.postgresql.Driver -Ddb.default.url=${DATABASE_URL} -Dconfig.resource=heroku.conf -Dlogger.file=conf/heroku.logback.xml
31 changes: 31 additions & 0 deletions SecurityAnnotations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Security Annotations
Annotations to use in Controllers to manage access control

*Restrict to Role Groups*
By using the following annotation on top of a Controller method, the corresponding endpoint is accessible only to
users that have the list of ROLES specified in the list

```java
@Restrict(@Group(GlobalData.USER_ROLE)) // only those with the USER_ROLE
@Restrict(@Group(GlobalData.USER_ROLE),@Group(GlobalData.ADMIN_ROLE)) // only those with the USER_ROLE or the ADMIN_ROLE
@Restrict(@Group(GlobalData.USER_ROLE,GlobalData.ADMIN_ROLE)) // only those with the USER_ROLE AND ADMIN_ROLE
```

*Dynamic Access Control*
Refer to access rules that are more specific and are implemented by the DynamicResourceHandlers in the security package.
You can use them via the following annotation, where value indicates which of the dynamic handlers to use (as they
are defined in security.MyDynamicResourceHandler) :

```java
@Dynamic(value = "OnlyMe", meta = SecurityModelConstants.USER_RESOURCE_PATH)
```

As of now, the following dynamic handlers are avaiable:

* *MemberOfAssembly:* the user that sent the request is member of the assembly to which the resource belongs
* *CoordinatorOfGroup:* the user that sent the request is coordinator of the working group to which the resource belongs
* *CoordinatorOfAssembly:* the user that sent the request is member of the assembly to which the resource belongs
* *CanInviteToGroup:* the user has permission to invite others to the group
* *CanInviteToAssembly:* the user has permission to invete other to the assembly
* *OnlyMeAndAdmin:* the request will be successful only if the requested is under the user's resource space or if he/she is ADMIN
* *OnlyMe:* the request will be successful only if the requested is under the user's resource space
70 changes: 66 additions & 4 deletions app/Global.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,25 @@
import play.Logger;
import play.Play;
import play.libs.Yaml;
import play.mvc.Call;

import com.avaje.ebean.Ebean;
import com.feth.play.module.pa.PlayAuthenticate;
import com.feth.play.module.pa.PlayAuthenticate.Resolver;
import com.feth.play.module.pa.exceptions.AccessDeniedException;
import com.feth.play.module.pa.exceptions.AuthException;

import controllers.routes;

public class Global extends GlobalSettings {

public void onStart(Application app) {
Logger.info("Application has started");
initializeData(app);
initializeAuthenticationResolver();
}

private void initializeData(Application app) {
/**
* If the project configuration ask for the database to be clean,
* rebuild it (for dev purposes only)
Expand All @@ -31,10 +42,61 @@ public void onStart(Application app) {
cleanDBAndRebuild(app);
}

List<String> dataLoadFiles = Play.application().configuration()
.getStringList("appcivist.db.initial-data.files");
Boolean doNotLoadData = Play.application().configuration()
.getBoolean("appcivist.db.noInitialData");
if(doNotLoadData==null || !doNotLoadData) {
List<String> dataLoadFiles = Play.application().configuration()
.getStringList("appcivist.db.initial-data.files");

loadDataFiles(dataLoadFiles);
}
}

private void initializeAuthenticationResolver() {
Logger.info("Initialiazing PlayAuthenticate Resolver");
PlayAuthenticate.setResolver(new Resolver() {

public Call login() {
// Your login page
return routes.Users.login();
}

public Call afterAuth() {
// The user will be redirected to this page after authentication
// if no original URL was saved
return routes.Application.index();
}

public Call afterLogout() {
return routes.Application.index();
}

loadDataFiles(dataLoadFiles);
public Call auth(final String provider) {
// You can provide your own authentication implementation,
// however the default should be sufficient for most cases
return routes.AuthenticateLocal
.authenticate(provider);
}

public Call askMerge() {
return routes.Users.askMerge();
}

public Call askLink() {
return routes.Users.askLink();
}

public Call onException(final AuthException e) {
if (e instanceof AccessDeniedException) {
return routes.Users
.oAuthDenied(((AccessDeniedException) e)
.getProviderKey());
}

// more custom problem handling here...
return super.onException(e);
}
});
}

@SuppressWarnings("rawtypes")
Expand All @@ -46,7 +108,7 @@ private void loadDataFiles(List<String> dataLoadFiles) {
for (String dataFile : dataLoadFiles) {
InitialDataConfig fileConfig = InitialDataConfig
.readByFileName(dataFile);

Logger.info("---> AppCivist '" + dataFile + "' loading registry: "+fileConfig);
if (fileConfig == null || !fileConfig.getLoaded()) {
try {
Logger.info("---> AppCivist: Loading '" + dataFile
Expand Down
42 changes: 34 additions & 8 deletions app/controllers/Assemblies.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

import com.feth.play.module.pa.PlayAuthenticate;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiImplicitParam;
import com.wordnik.swagger.annotations.ApiImplicitParams;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiResponse;
import com.wordnik.swagger.annotations.ApiResponses;
Expand All @@ -39,16 +41,33 @@ public class Assemblies extends Controller {
public static final Form<Assembly> ASSEMBLY_FORM = form(Assembly.class);
public static final Form<TransferMembership> MEMBERSHIP_FORM = form(TransferMembership.class);

// @ApiOperation(httpMethod = "PUT", response = TransferResponseStatus.class, produces = "application/json", value = "Update user information", notes = "Updates user information")
// @ApiResponses(value = { @ApiResponse(code = 404, message = "User not found", response=TransferResponseStatus.class) })
// @ApiImplicitParams({
// @ApiImplicitParam(name="uid", value="User's ID", dataType="Long", paramType="path"),
// @ApiImplicitParam(name="SESSION_KEY", value="User's session authentication key", dataType="String", paramType="header"),
// @ApiImplicitParam(name="user", value="User's updated information", dataType="models.User", paramType = "body")
// })

/**
* Return the full list of assemblies
*
* @return models.AssemblyCollection
*/
@ApiOperation(httpMethod = "GET", response = Assembly.class, produces = "application/json", value = "Get list of assemblies", notes = "Get the full list of assemblies. Only availabe to ADMINS")
@Restrict({ @Group(GlobalData.ADMIN_ROLE) })
@ApiOperation(httpMethod = "GET", response = Assembly.class, responseContainer = "List", produces = "application/json", value = "Get list of assemblies based on query", notes = "Get the full list of assemblies. Only availabe to ADMINS")
@ApiResponses(value = {
@ApiResponse(code = 404, message = "No assembly found", response=TransferResponseStatus.class)
})
@ApiImplicitParams({
@ApiImplicitParam(name="query", value="Search query string (keywords in title)", dataType="String", paramType="query"),
@ApiImplicitParam(name="filter", value="Special filters. 'summary' returns only summarized info of assemblies, 'featured' returns a list of marks featured assemblies and 'nearby' limits the query to assemblies that are nearby of the user location, ", dataType="String", paramType="query", allowableValues="featured,nearby,summary,random"),
@ApiImplicitParam(name="SESSION_KEY", value="User's session authentication key", dataType="String", paramType="header")
// @ApiImplicitParam(name="user", value="User's updated information", dataType="models.User", paramType = "body")
})
@Restrict({ @Group(GlobalData.USER_ROLE) })
public static Result findAssemblies(String query, String filter) {
if (query!=null && !query.isEmpty()) return searchAssemblies(query);
else if (filter!=null && !filter.isEmpty()) return findAssembliesWithFilter(filter);
else if (filter!=null && !filter.isEmpty()) return findAssembliesWithFilter(query,filter);
else return ok(Json.toJson(Assembly.findAll()));
}

Expand All @@ -59,6 +78,7 @@ public static Result findAssemblies(String query, String filter) {
*/
@Restrict({ @Group(GlobalData.USER_ROLE) })
public static Result searchAssemblies(String query) {
// TODO: use also the filter
List<Assembly> a = Assembly.findBySimilarName(query);
if (a!=null) return ok(Json.toJson(a));
else return notFound(Json.toJson(TransferResponseStatus.noDataMessage("No assemblies with a title resembling query", "")));
Expand All @@ -71,12 +91,18 @@ public static Result searchAssemblies(String query) {
* @return models.AssemblyCollection
*/
@Restrict({ @Group(GlobalData.USER_ROLE) })
public static Result findAssembliesWithFilter(String filter) {
public static Result findAssembliesWithFilter(String query, String filter) {
List<Assembly> a = null;
if (filter.equals("featured")) {
List<Assembly> a = Assembly.findFeaturedAssemblies();
if (a!=null) return ok(Json.toJson(a));
else return notFound(Json.toJson(TransferResponseStatus.noDataMessage("No featured assemblies", "")));
} else return notFound(Json.toJson(TransferResponseStatus.noDataMessage("Not implemented yet", "")));
a = Assembly.findFeaturedAssemblies(query);
} else if (filter.equals("random")) {
a = Assembly.findRandomAssemblies(query);
// TODO: } else if (filter.equals("nearby")) { return Assembly.findNearbyAssemblies();
// TODO: } else if (filter.equals("summary")) { return Assembly.findAssembliesSummaries(query);
} else return internalServerError(Json.toJson(TransferResponseStatus.noDataMessage("Filter '"+filter+"' is not supported yet", "")));

if (a!=null) return ok(Json.toJson(a));
else return notFound(Json.toJson(TransferResponseStatus.noDataMessage("No "+filter+" assemblies", "")));
}

@ApiOperation(response = Assembly.class, produces = "application/json", value = "Create a new assembly")
Expand Down
101 changes: 101 additions & 0 deletions app/controllers/Campaigns.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,23 @@

import com.feth.play.module.pa.PlayAuthenticate;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiImplicitParam;
import com.wordnik.swagger.annotations.ApiImplicitParams;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiResponse;
import com.wordnik.swagger.annotations.ApiResponses;

import enums.AppcivistNotificationTypes;
import enums.AppcivistResourceTypes;
import http.Headers;
import models.Assembly;
import models.Campaign;
import models.CampaignPhase;
import models.CampaignPhaseMilestone;
import models.Contribution;
import models.Membership;
import models.MembershipAssembly;
import models.ResourceSpace;
import models.User;
import play.Logger;
import play.data.Form;
Expand All @@ -22,8 +34,14 @@
import utils.GlobalData;
import models.transfer.TransferMembership;
import models.transfer.TransferResponseStatus;
import models.transfer.TransferUpdate;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import static play.data.Form.form;

Expand Down Expand Up @@ -125,4 +143,87 @@ public static Result createCampaign(Long aid) {
return ok(Json.toJson(responseBody));
}
}

@ApiOperation(httpMethod = "GET", response = TransferUpdate.class, responseContainer="List", produces = "application/json", value = "Update user information", notes = "Updates user information")
@ApiResponses(value = { @ApiResponse(code = 404, message = "User not found", response=TransferResponseStatus.class) })
@ApiImplicitParams({
//@ApiImplicitParam(name="user", value="user", dataType="String", defaultValue="user", paramType = "path"),
@ApiImplicitParam(name="uuid", value="Assembly's UUID", dataType="java.util.UUID", paramType="path"),
@ApiImplicitParam(name="SESSION_KEY", value="User's session authentication key", dataType="String", paramType="header"),
@ApiImplicitParam(name="filter", value="Filter value", dataType="String", paramType="query", allowableValues="ongoing,past,future,all", defaultValue="ongoing")
})
@Dynamic(value = "OnlyMe", meta = SecurityModelConstants.USER_RESOURCE_PATH)
public static Result campaignsByAssembly(UUID uuid, String filter) {
if (filter==null || filter.equals("ongoing")) {
return ongoingCampaignsByAssembly(uuid);
} else if (filter.equals("past")) {
return internalServerError(Json.toJson(new TransferResponseStatus("Not implemented")));
} else if (filter.equals("future")) {
return internalServerError(Json.toJson(new TransferResponseStatus("Not implemented")));
} else {
Assembly a = Assembly.readByUUID(uuid);
List<Campaign> campaigns = a.getResources().getCampaigns();
if (!campaigns.isEmpty()) return ok(Json.toJson(campaigns));
else
return notFound(Json.toJson(new TransferResponseStatus("No ongoing campaigns")));
}
}

@ApiOperation(httpMethod = "GET", response = Campaign.class, responseContainer="List", produces = "application/json", value = "Update user information", notes = "Updates user information")
@ApiResponses(value = { @ApiResponse(code = 404, message = "No Campaign Found", response=TransferResponseStatus.class) })
@ApiImplicitParams({
//@ApiImplicitParam(name="user", value="user", dataType="String", defaultValue="user", paramType = "path"),
@ApiImplicitParam(name="uuid", value="User's UUID", dataType="java.util.UUID", paramType="path"),
@ApiImplicitParam(name="SESSION_KEY", value="User's session authentication key", dataType="String", paramType="header"),
@ApiImplicitParam(name="filter", value="Filter value", dataType="String", paramType="query", allowableValues="ongoing,past,future,all", defaultValue="ongoing")
})
@Dynamic(value = "OnlyMe", meta = SecurityModelConstants.USER_RESOURCE_PATH)
public static Result campaignsByUser(UUID uuid, String filter) {
if (filter==null || filter.equals("ongoing")) {
return ongoingCampaignsByUser(uuid);
} else if (filter.equals("past")) {
return internalServerError(Json.toJson(new TransferResponseStatus("Not implemented")));
} else if (filter.equals("future")) {
return internalServerError(Json.toJson(new TransferResponseStatus("Not implemented")));
} else {
User u = User.findByUUID(uuid);
List<Membership> assemblyMemberships = Membership.findByUser(u, "ASSEMBLY");
List<Campaign> campaigns = new ArrayList<Campaign>();
for (Membership membership : assemblyMemberships) {
Assembly a = ((MembershipAssembly) membership).getAssembly();
campaigns.addAll(a.getResources().getCampaigns());
}
if (!campaigns.isEmpty()) return ok(Json.toJson(campaigns));
else
return notFound(Json.toJson(new TransferResponseStatus("No ongoing campaigns")));
}
}

private static Result ongoingCampaignsByUser(UUID uuid) {
User u = User.findByUUID(uuid);
List<Membership> assemblyMemberships = Membership.findByUser(u, "ASSEMBLY");
List<Campaign> ongoingCampaigns = new ArrayList<Campaign>();

for (Membership membership : assemblyMemberships) {
Assembly a = ((MembershipAssembly) membership).getAssembly();
ongoingCampaigns.addAll(Campaign.extractOngoingCampaignsFromAssembly(a));
}

if (!ongoingCampaigns.isEmpty()) return ok(Json.toJson(ongoingCampaigns));
else
return notFound(Json.toJson(new TransferResponseStatus("No ongoing campaigns")));
}





private static Result ongoingCampaignsByAssembly(UUID uuid) {
Assembly a = Assembly.readByUUID(uuid);
List<Campaign> ongoingCampaigns = new ArrayList<Campaign>();
ongoingCampaigns.addAll(Campaign.extractOngoingCampaignsFromAssembly(a));
if (!ongoingCampaigns.isEmpty()) return ok(Json.toJson(ongoingCampaigns));
else
return notFound(Json.toJson(new TransferResponseStatus("No ongoing campaigns")));
}
}
Loading

0 comments on commit 9811ffc

Please sign in to comment.