ResourceSetRegistrationEndpoint.java

  1. /*******************************************************************************
  2.  * Copyright 2017 The MIT Internet Trust Consortium
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  *   http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  *******************************************************************************/
  16. package org.mitre.uma.web;


  17. import static org.mitre.oauth2.web.AuthenticationUtilities.ensureOAuthScope;
  18. import static org.mitre.util.JsonUtils.getAsLong;
  19. import static org.mitre.util.JsonUtils.getAsString;
  20. import static org.mitre.util.JsonUtils.getAsStringSet;

  21. import java.util.Collection;
  22. import java.util.Collections;
  23. import java.util.HashSet;
  24. import java.util.Set;

  25. import org.mitre.oauth2.model.SystemScope;
  26. import org.mitre.oauth2.service.SystemScopeService;
  27. import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
  28. import org.mitre.openid.connect.view.HttpCodeView;
  29. import org.mitre.openid.connect.view.JsonEntityView;
  30. import org.mitre.openid.connect.view.JsonErrorView;
  31. import org.mitre.uma.model.ResourceSet;
  32. import org.mitre.uma.service.ResourceSetService;
  33. import org.mitre.uma.view.ResourceSetEntityAbbreviatedView;
  34. import org.mitre.uma.view.ResourceSetEntityView;
  35. import org.slf4j.Logger;
  36. import org.slf4j.LoggerFactory;
  37. import org.springframework.beans.factory.annotation.Autowired;
  38. import org.springframework.http.HttpStatus;
  39. import org.springframework.security.access.prepost.PreAuthorize;
  40. import org.springframework.security.core.Authentication;
  41. import org.springframework.security.oauth2.provider.OAuth2Authentication;
  42. import org.springframework.stereotype.Controller;
  43. import org.springframework.ui.Model;
  44. import org.springframework.util.MimeTypeUtils;
  45. import org.springframework.web.bind.annotation.PathVariable;
  46. import org.springframework.web.bind.annotation.RequestBody;
  47. import org.springframework.web.bind.annotation.RequestMapping;
  48. import org.springframework.web.bind.annotation.RequestMethod;

  49. import com.google.common.base.Strings;
  50. import com.google.gson.JsonElement;
  51. import com.google.gson.JsonObject;
  52. import com.google.gson.JsonParseException;
  53. import com.google.gson.JsonParser;

  54. @Controller
  55. @RequestMapping("/" + ResourceSetRegistrationEndpoint.URL)
  56. @PreAuthorize("hasRole('ROLE_USER')")
  57. public class ResourceSetRegistrationEndpoint {

  58.     private static final Logger logger = LoggerFactory.getLogger(ResourceSetRegistrationEndpoint.class);

  59.     public static final String DISCOVERY_URL = "resource_set";
  60.     public static final String URL = DISCOVERY_URL + "/resource_set";

  61.     @Autowired
  62.     private ResourceSetService resourceSetService;

  63.     @Autowired
  64.     private ConfigurationPropertiesBean config;

  65.     @Autowired
  66.     private SystemScopeService scopeService;

  67.     private JsonParser parser = new JsonParser();

  68.     @RequestMapping(method = RequestMethod.POST, produces = MimeTypeUtils.APPLICATION_JSON_VALUE, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE)
  69.     public String createResourceSet(@RequestBody String jsonString, Model m, Authentication auth) {
  70.         ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE);

  71.         ResourceSet rs = parseResourceSet(jsonString);

  72.         if (rs == null) { // there was no resource set in the body
  73.             logger.warn("Resource set registration missing body.");

  74.             m.addAttribute("code", HttpStatus.BAD_REQUEST);
  75.             m.addAttribute("error_description", "Resource request was missing body.");
  76.             return JsonErrorView.VIEWNAME;
  77.         }

  78.         if (auth instanceof OAuth2Authentication) {
  79.             // if it's an OAuth mediated call, it's on behalf of a client, so store that
  80.             OAuth2Authentication o2a = (OAuth2Authentication) auth;
  81.             rs.setClientId(o2a.getOAuth2Request().getClientId());
  82.             rs.setOwner(auth.getName()); // the username is going to be in the auth object
  83.         } else {
  84.             // this one shouldn't be called if it's not OAuth
  85.             m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
  86.             m.addAttribute(JsonErrorView.ERROR_MESSAGE, "This call must be made with an OAuth token");
  87.             return JsonErrorView.VIEWNAME;
  88.         }

  89.         rs = validateScopes(rs);

  90.         if (Strings.isNullOrEmpty(rs.getName()) // there was no name (required)
  91.                 || rs.getScopes() == null // there were no scopes (required)
  92.                 ) {

  93.             logger.warn("Resource set registration missing one or more required fields.");

  94.             m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
  95.             m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Resource request was missing one or more required fields.");
  96.             return JsonErrorView.VIEWNAME;
  97.         }

  98.         ResourceSet saved = resourceSetService.saveNew(rs);

  99.         m.addAttribute(HttpCodeView.CODE, HttpStatus.CREATED);
  100.         m.addAttribute(JsonEntityView.ENTITY, saved);
  101.         m.addAttribute(ResourceSetEntityAbbreviatedView.LOCATION, config.getIssuer() + URL + "/" + saved.getId());

  102.         return ResourceSetEntityAbbreviatedView.VIEWNAME;

  103.     }

  104.     @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
  105.     public String readResourceSet(@PathVariable ("id") Long id, Model m, Authentication auth) {
  106.         ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE);

  107.         ResourceSet rs = resourceSetService.getById(id);

  108.         if (rs == null) {
  109.             m.addAttribute("code", HttpStatus.NOT_FOUND);
  110.             m.addAttribute("error", "not_found");
  111.             return JsonErrorView.VIEWNAME;
  112.         } else {

  113.             rs = validateScopes(rs);

  114.             if (!auth.getName().equals(rs.getOwner())) {

  115.                 logger.warn("Unauthorized resource set request from wrong user; expected " + rs.getOwner() + " got " + auth.getName());

  116.                 // it wasn't issued to this user
  117.                 m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN);
  118.                 return JsonErrorView.VIEWNAME;
  119.             } else {
  120.                 m.addAttribute(JsonEntityView.ENTITY, rs);
  121.                 return ResourceSetEntityView.VIEWNAME;
  122.             }

  123.         }

  124.     }

  125.     @RequestMapping(value = "/{id}", method = RequestMethod.PUT, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
  126.     public String updateResourceSet(@PathVariable ("id") Long id, @RequestBody String jsonString, Model m, Authentication auth) {
  127.         ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE);

  128.         ResourceSet newRs = parseResourceSet(jsonString);

  129.         if (newRs == null // there was no resource set in the body
  130.                 || Strings.isNullOrEmpty(newRs.getName()) // there was no name (required)
  131.                 || newRs.getScopes() == null // there were no scopes (required)
  132.                 || newRs.getId() == null || !newRs.getId().equals(id) // the IDs didn't match
  133.                 ) {

  134.             logger.warn("Resource set registration missing one or more required fields.");

  135.             m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
  136.             m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Resource request was missing one or more required fields.");
  137.             return JsonErrorView.VIEWNAME;
  138.         }

  139.         ResourceSet rs = resourceSetService.getById(id);

  140.         if (rs == null) {
  141.             m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND);
  142.             m.addAttribute(JsonErrorView.ERROR, "not_found");
  143.             return JsonErrorView.VIEWNAME;
  144.         } else {
  145.             if (!auth.getName().equals(rs.getOwner())) {

  146.                 logger.warn("Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName());

  147.                 // it wasn't issued to this user
  148.                 m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN);
  149.                 return JsonErrorView.VIEWNAME;
  150.             } else {

  151.                 ResourceSet saved = resourceSetService.update(rs, newRs);

  152.                 m.addAttribute(JsonEntityView.ENTITY, saved);
  153.                 m.addAttribute(ResourceSetEntityAbbreviatedView.LOCATION, config.getIssuer() + URL + "/" + rs.getId());
  154.                 return ResourceSetEntityAbbreviatedView.VIEWNAME;
  155.             }

  156.         }
  157.     }

  158.     @RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
  159.     public String deleteResourceSet(@PathVariable ("id") Long id, Model m, Authentication auth) {
  160.         ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE);

  161.         ResourceSet rs = resourceSetService.getById(id);

  162.         if (rs == null) {
  163.             m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND);
  164.             m.addAttribute(JsonErrorView.ERROR, "not_found");
  165.             return JsonErrorView.VIEWNAME;
  166.         } else {
  167.             if (!auth.getName().equals(rs.getOwner())) {

  168.                 logger.warn("Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName());

  169.                 // it wasn't issued to this user
  170.                 m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN);
  171.                 return JsonErrorView.VIEWNAME;
  172.             } else if (auth instanceof OAuth2Authentication &&
  173.                     !((OAuth2Authentication)auth).getOAuth2Request().getClientId().equals(rs.getClientId())){

  174.                 logger.warn("Unauthorized resource set request from bad client; expected " + rs.getClientId() + " got " + ((OAuth2Authentication)auth).getOAuth2Request().getClientId());

  175.                 // it wasn't issued to this client
  176.                 m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN);
  177.                 return JsonErrorView.VIEWNAME;
  178.             } else {

  179.                 // user and client matched
  180.                 resourceSetService.remove(rs);

  181.                 m.addAttribute(HttpCodeView.CODE, HttpStatus.NO_CONTENT);
  182.                 return HttpCodeView.VIEWNAME;
  183.             }

  184.         }
  185.     }

  186.     @RequestMapping(method = RequestMethod.GET, produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
  187.     public String listResourceSets(Model m, Authentication auth) {
  188.         ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE);

  189.         String owner = auth.getName();

  190.         Collection<ResourceSet> resourceSets = Collections.emptySet();
  191.         if (auth instanceof OAuth2Authentication) {
  192.             // if it's an OAuth mediated call, it's on behalf of a client, so look that up too
  193.             OAuth2Authentication o2a = (OAuth2Authentication) auth;
  194.             resourceSets = resourceSetService.getAllForOwnerAndClient(owner, o2a.getOAuth2Request().getClientId());
  195.         } else {
  196.             // otherwise get everything for the current user
  197.             resourceSets = resourceSetService.getAllForOwner(owner);
  198.         }

  199.         // build the entity here and send to the display

  200.         Set<String> ids = new HashSet<>();
  201.         for (ResourceSet resourceSet : resourceSets) {
  202.             ids.add(resourceSet.getId().toString()); // add them all as strings so that gson renders them properly
  203.         }

  204.         m.addAttribute(JsonEntityView.ENTITY, ids);
  205.         return JsonEntityView.VIEWNAME;
  206.     }

  207.     private ResourceSet parseResourceSet(String jsonString) {

  208.         try {
  209.             JsonElement el = parser.parse(jsonString);

  210.             if (el.isJsonObject()) {
  211.                 JsonObject o = el.getAsJsonObject();

  212.                 ResourceSet rs = new ResourceSet();
  213.                 rs.setId(getAsLong(o, "_id"));
  214.                 rs.setName(getAsString(o, "name"));
  215.                 rs.setIconUri(getAsString(o, "icon_uri"));
  216.                 rs.setType(getAsString(o, "type"));
  217.                 rs.setScopes(getAsStringSet(o, "scopes"));
  218.                 rs.setUri(getAsString(o, "uri"));

  219.                 return rs;

  220.             }

  221.             return null;

  222.         } catch (JsonParseException e) {
  223.             return null;
  224.         }

  225.     }


  226.     /**
  227.      *
  228.      * Make sure the resource set doesn't have any restricted or reserved scopes.
  229.      *
  230.      * @param rs
  231.      */
  232.     private ResourceSet validateScopes(ResourceSet rs) {
  233.         // scopes that the client is asking for
  234.         Set<SystemScope> requestedScopes = scopeService.fromStrings(rs.getScopes());

  235.         // the scopes that the resource set can have must be a subset of the dynamically allowed scopes
  236.         Set<SystemScope> allowedScopes = scopeService.removeRestrictedAndReservedScopes(requestedScopes);

  237.         rs.setScopes(scopeService.toStrings(allowedScopes));

  238.         return rs;
  239.     }

  240. }