From 3b6412219b170cd7f4a2842fc2b712bb4aabede7 Mon Sep 17 00:00:00 2001 From: Justin Richer Date: Tue, 24 Feb 2015 15:10:48 -0500 Subject: [PATCH] added abbreviated view, updated OAuth error handling, fixed URL mapping --- .../webapp/WEB-INF/application-context.xml | 2 +- .../ResourceSetEntityAbbreviatedView.java | 13 ++- .../connect/view/ResourceSetEntityView.java | 103 ++++++++++++++++++ .../web/ResourceSetRegistrationEndpoint.java | 77 ++++++++++--- 4 files changed, 178 insertions(+), 17 deletions(-) create mode 100644 openid-connect-server/src/main/java/org/mitre/openid/connect/view/ResourceSetEntityView.java diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/application-context.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/application-context.xml index ee31aaaec..fd234f069 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/application-context.xml +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/application-context.xml @@ -117,7 +117,7 @@ - + diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ResourceSetEntityAbbreviatedView.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ResourceSetEntityAbbreviatedView.java index bb8238763..bd5a4e5f2 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ResourceSetEntityAbbreviatedView.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ResourceSetEntityAbbreviatedView.java @@ -12,22 +12,24 @@ import org.mitre.openid.connect.model.ResourceSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.web.servlet.view.AbstractView; +import com.google.common.base.Strings; import com.google.gson.ExclusionStrategy; import com.google.gson.FieldAttributes; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonObject; -@Component(ResourceSetEntityView.VIEWNAME) -public class ResourceSetEntityView extends AbstractView { +@Component(ResourceSetEntityAbbreviatedView.VIEWNAME) +public class ResourceSetEntityAbbreviatedView extends AbstractView { private static Logger logger = LoggerFactory.getLogger(JsonEntityView.class); - public static final String VIEWNAME = "resourceSetEntityView"; + public static final String VIEWNAME = "resourceSetEntityAbbreviatedView"; @Autowired private ConfigurationPropertiesBean config; @@ -68,6 +70,11 @@ public class ResourceSetEntityView extends AbstractView { response.setStatus(code.value()); + String location = (String) model.get("location"); + if (!Strings.isNullOrEmpty(location)) { + response.setHeader(HttpHeaders.LOCATION, location); + } + try { Writer out = response.getWriter(); diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ResourceSetEntityView.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ResourceSetEntityView.java new file mode 100644 index 000000000..c53392a77 --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ResourceSetEntityView.java @@ -0,0 +1,103 @@ +package org.mitre.openid.connect.view; + +import java.io.IOException; +import java.io.Writer; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.mitre.openid.connect.config.ConfigurationPropertiesBean; +import org.mitre.openid.connect.model.ResourceSet; +import org.mitre.util.JsonUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.web.servlet.view.AbstractView; + +import com.google.common.base.Strings; +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; + +@Component(ResourceSetEntityView.VIEWNAME) +public class ResourceSetEntityView extends AbstractView { + private static Logger logger = LoggerFactory.getLogger(JsonEntityView.class); + + public static final String VIEWNAME = "resourceSetEntityView"; + + @Autowired + private ConfigurationPropertiesBean config; + + private Gson gson = new GsonBuilder() + .setExclusionStrategies(new ExclusionStrategy() { + + @Override + public boolean shouldSkipField(FieldAttributes f) { + + return false; + } + + @Override + public boolean shouldSkipClass(Class clazz) { + // skip the JPA binding wrapper + if (clazz.equals(BeanPropertyBindingResult.class)) { + return true; + } + return false; + } + + }) + .serializeNulls() + .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") + .create(); + + @Override + protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) { + + response.setContentType("application/json"); + + + HttpStatus code = (HttpStatus) model.get("code"); + if (code == null) { + code = HttpStatus.OK; // default to 200 + } + + response.setStatus(code.value()); + + String location = (String) model.get("location"); + if (!Strings.isNullOrEmpty(location)) { + response.setHeader(HttpHeaders.LOCATION, location); + } + + try { + + Writer out = response.getWriter(); + ResourceSet rs = (ResourceSet) model.get("entity"); + + JsonObject o = new JsonObject(); + + o.addProperty("_id", rs.getId()); + o.addProperty("user_access_policy_uri", config.getIssuer() + "manage/resource/" + rs.getId()); + o.addProperty("name", rs.getName()); + o.addProperty("uri", rs.getUri()); + o.addProperty("type", rs.getType()); + o.add("scopes", JsonUtils.getAsArray(rs.getScopes())); + o.addProperty("icon_uri", rs.getIconUri()); + + gson.toJson(o, out); + + } catch (IOException e) { + + logger.error("IOException in ResourceSetEntityView.java: ", e); + + } + } + +} diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ResourceSetRegistrationEndpoint.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ResourceSetRegistrationEndpoint.java index b823d44fa..f48ee0e08 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ResourceSetRegistrationEndpoint.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ResourceSetRegistrationEndpoint.java @@ -16,64 +16,78 @@ *******************************************************************************/ package org.mitre.openid.connect.web; -import static org.mitre.util.JsonUtils.getAsString; +import static org.mitre.util.JsonUtils.*; import static org.mitre.util.JsonUtils.getAsStringSet; import org.mitre.oauth2.service.SystemScopeService; +import org.mitre.openid.connect.config.ConfigurationPropertiesBean; import org.mitre.openid.connect.model.ResourceSet; import org.mitre.openid.connect.service.ResourceSetService; import org.mitre.openid.connect.view.JsonErrorView; +import org.mitre.openid.connect.view.ResourceSetEntityAbbreviatedView; import org.mitre.openid.connect.view.ResourceSetEntityView; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.util.MimeTypeUtils; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableSet; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; @Controller -@RequestMapping(ResourceSetRegistrationEndpoint.URL) +@RequestMapping("/" + ResourceSetRegistrationEndpoint.URL) @PreAuthorize("hasRole('ROLE_USER')") public class ResourceSetRegistrationEndpoint { - public static final String URL = "/resource_set/resource_set"; + private static final Logger logger = LoggerFactory.getLogger(ResourceSetRegistrationEndpoint.class); + + public static final String URL = "resource_set/resource_set"; @Autowired private ResourceSetService resourceSetService; + @Autowired + private ConfigurationPropertiesBean config; + private JsonParser parser = new JsonParser(); @RequestMapping(method = RequestMethod.POST, produces = MimeTypeUtils.APPLICATION_JSON_VALUE, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE) public String createResourceSet(@RequestBody String jsonString, Model m, Authentication auth) { - // if auth is OAuth, make sure we've got the right scope if (auth instanceof OAuth2Authentication) { OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) auth; if (oAuth2Authentication.getOAuth2Request().getScope() == null || oAuth2Authentication.getOAuth2Request().getScope().contains(SystemScopeService.RESOURCE_SET_REGISTRATION_SCOPE)) { - - // it was an OAuth2 request but it didn't have the right scope - m.addAttribute("code", HttpStatus.FORBIDDEN); - return JsonErrorView.VIEWNAME; - + throw new InsufficientScopeException("Insufficient scope", ImmutableSet.of(SystemScopeService.RESOURCE_SET_REGISTRATION_SCOPE)); } } ResourceSet rs = parseResourceSet(jsonString); - if (rs == null) { - // there was no resource set in the body + if (rs == null // there was no resource set in the body + || Strings.isNullOrEmpty(rs.getName()) // there was no name (required) + || rs.getScopes() == null // there were no scopes (required) + ) { + + logger.warn("Resource set registration missing one or more required fields."); + m.addAttribute("code", HttpStatus.BAD_REQUEST); + m.addAttribute("error_description", "Resource request was missing one or more required fields."); return JsonErrorView.VIEWNAME; } @@ -83,10 +97,46 @@ public class ResourceSetRegistrationEndpoint { m.addAttribute("code", HttpStatus.CREATED); m.addAttribute("entity", saved); - return ResourceSetEntityView.VIEWNAME; + m.addAttribute("location", config.getIssuer() + URL + "/" + rs.getId()); + + return ResourceSetEntityAbbreviatedView.VIEWNAME; } + @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) + public String readResourceSet(@PathVariable ("id") Long id, Model m, Authentication auth) { + // if auth is OAuth, make sure we've got the right scope + if (auth instanceof OAuth2Authentication) { + OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) auth; + if (oAuth2Authentication.getOAuth2Request().getScope() == null + || oAuth2Authentication.getOAuth2Request().getScope().contains(SystemScopeService.RESOURCE_SET_REGISTRATION_SCOPE)) { + throw new InsufficientScopeException("Insufficient scope", ImmutableSet.of(SystemScopeService.RESOURCE_SET_REGISTRATION_SCOPE)); + } + } + + ResourceSet rs = resourceSetService.getById(id); + + if (rs == null) { + m.addAttribute("code", HttpStatus.NOT_FOUND); + m.addAttribute("error", "not_found"); + return JsonErrorView.VIEWNAME; + } else { + + if (!auth.getName().equals(rs.getOwner())) { + + logger.warn("Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName()); + + // it wasn't issued to this user + m.addAttribute("code", HttpStatus.FORBIDDEN); + return JsonErrorView.VIEWNAME; + } else { + m.addAttribute("entity", rs); + return ResourceSetEntityView.VIEWNAME; + } + + } + + } private ResourceSet parseResourceSet(String jsonString) { @@ -97,10 +147,11 @@ public class ResourceSetRegistrationEndpoint { JsonObject o = el.getAsJsonObject(); ResourceSet rs = new ResourceSet(); + rs.setId(getAsLong(o, "_id")); rs.setName(getAsString(o, "name")); rs.setIconUri(getAsString(o, "icon_uri")); rs.setType(getAsString(o, "type")); - rs.setScopes(getAsStringSet(o, "scope")); + rs.setScopes(getAsStringSet(o, "scopes")); rs.setUri(getAsString(o, "uri")); return rs;