ResourceSetRegistrationEndpoint.java
/*******************************************************************************
* Copyright 2017 The MIT Internet Trust Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package org.mitre.uma.web;
import static org.mitre.oauth2.web.AuthenticationUtilities.ensureOAuthScope;
import static org.mitre.util.JsonUtils.getAsLong;
import static org.mitre.util.JsonUtils.getAsString;
import static org.mitre.util.JsonUtils.getAsStringSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.mitre.oauth2.model.SystemScope;
import org.mitre.oauth2.service.SystemScopeService;
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
import org.mitre.openid.connect.view.HttpCodeView;
import org.mitre.openid.connect.view.JsonEntityView;
import org.mitre.openid.connect.view.JsonErrorView;
import org.mitre.uma.model.ResourceSet;
import org.mitre.uma.service.ResourceSetService;
import org.mitre.uma.view.ResourceSetEntityAbbreviatedView;
import org.mitre.uma.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.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.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
@Controller
@RequestMapping("/" + ResourceSetRegistrationEndpoint.URL)
@PreAuthorize("hasRole('ROLE_USER')")
public class ResourceSetRegistrationEndpoint {
private static final Logger logger = LoggerFactory.getLogger(ResourceSetRegistrationEndpoint.class);
public static final String DISCOVERY_URL = "resource_set";
public static final String URL = DISCOVERY_URL + "/resource_set";
@Autowired
private ResourceSetService resourceSetService;
@Autowired
private ConfigurationPropertiesBean config;
@Autowired
private SystemScopeService scopeService;
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) {
ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE);
ResourceSet rs = parseResourceSet(jsonString);
if (rs == null) { // there was no resource set in the body
logger.warn("Resource set registration missing body.");
m.addAttribute("code", HttpStatus.BAD_REQUEST);
m.addAttribute("error_description", "Resource request was missing body.");
return JsonErrorView.VIEWNAME;
}
if (auth instanceof OAuth2Authentication) {
// if it's an OAuth mediated call, it's on behalf of a client, so store that
OAuth2Authentication o2a = (OAuth2Authentication) auth;
rs.setClientId(o2a.getOAuth2Request().getClientId());
rs.setOwner(auth.getName()); // the username is going to be in the auth object
} else {
// this one shouldn't be called if it's not OAuth
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "This call must be made with an OAuth token");
return JsonErrorView.VIEWNAME;
}
rs = validateScopes(rs);
if (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(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Resource request was missing one or more required fields.");
return JsonErrorView.VIEWNAME;
}
ResourceSet saved = resourceSetService.saveNew(rs);
m.addAttribute(HttpCodeView.CODE, HttpStatus.CREATED);
m.addAttribute(JsonEntityView.ENTITY, saved);
m.addAttribute(ResourceSetEntityAbbreviatedView.LOCATION, config.getIssuer() + URL + "/" + saved.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) {
ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE);
ResourceSet rs = resourceSetService.getById(id);
if (rs == null) {
m.addAttribute("code", HttpStatus.NOT_FOUND);
m.addAttribute("error", "not_found");
return JsonErrorView.VIEWNAME;
} else {
rs = validateScopes(rs);
if (!auth.getName().equals(rs.getOwner())) {
logger.warn("Unauthorized resource set request from wrong user; expected " + rs.getOwner() + " got " + auth.getName());
// it wasn't issued to this user
m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN);
return JsonErrorView.VIEWNAME;
} else {
m.addAttribute(JsonEntityView.ENTITY, rs);
return ResourceSetEntityView.VIEWNAME;
}
}
}
@RequestMapping(value = "/{id}", method = RequestMethod.PUT, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
public String updateResourceSet(@PathVariable ("id") Long id, @RequestBody String jsonString, Model m, Authentication auth) {
ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE);
ResourceSet newRs = parseResourceSet(jsonString);
if (newRs == null // there was no resource set in the body
|| Strings.isNullOrEmpty(newRs.getName()) // there was no name (required)
|| newRs.getScopes() == null // there were no scopes (required)
|| newRs.getId() == null || !newRs.getId().equals(id) // the IDs didn't match
) {
logger.warn("Resource set registration missing one or more required fields.");
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Resource request was missing one or more required fields.");
return JsonErrorView.VIEWNAME;
}
ResourceSet rs = resourceSetService.getById(id);
if (rs == null) {
m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND);
m.addAttribute(JsonErrorView.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(HttpCodeView.CODE, HttpStatus.FORBIDDEN);
return JsonErrorView.VIEWNAME;
} else {
ResourceSet saved = resourceSetService.update(rs, newRs);
m.addAttribute(JsonEntityView.ENTITY, saved);
m.addAttribute(ResourceSetEntityAbbreviatedView.LOCATION, config.getIssuer() + URL + "/" + rs.getId());
return ResourceSetEntityAbbreviatedView.VIEWNAME;
}
}
}
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
public String deleteResourceSet(@PathVariable ("id") Long id, Model m, Authentication auth) {
ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE);
ResourceSet rs = resourceSetService.getById(id);
if (rs == null) {
m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND);
m.addAttribute(JsonErrorView.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(HttpCodeView.CODE, HttpStatus.FORBIDDEN);
return JsonErrorView.VIEWNAME;
} else if (auth instanceof OAuth2Authentication &&
!((OAuth2Authentication)auth).getOAuth2Request().getClientId().equals(rs.getClientId())){
logger.warn("Unauthorized resource set request from bad client; expected " + rs.getClientId() + " got " + ((OAuth2Authentication)auth).getOAuth2Request().getClientId());
// it wasn't issued to this client
m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN);
return JsonErrorView.VIEWNAME;
} else {
// user and client matched
resourceSetService.remove(rs);
m.addAttribute(HttpCodeView.CODE, HttpStatus.NO_CONTENT);
return HttpCodeView.VIEWNAME;
}
}
}
@RequestMapping(method = RequestMethod.GET, produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
public String listResourceSets(Model m, Authentication auth) {
ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE);
String owner = auth.getName();
Collection<ResourceSet> resourceSets = Collections.emptySet();
if (auth instanceof OAuth2Authentication) {
// if it's an OAuth mediated call, it's on behalf of a client, so look that up too
OAuth2Authentication o2a = (OAuth2Authentication) auth;
resourceSets = resourceSetService.getAllForOwnerAndClient(owner, o2a.getOAuth2Request().getClientId());
} else {
// otherwise get everything for the current user
resourceSets = resourceSetService.getAllForOwner(owner);
}
// build the entity here and send to the display
Set<String> ids = new HashSet<>();
for (ResourceSet resourceSet : resourceSets) {
ids.add(resourceSet.getId().toString()); // add them all as strings so that gson renders them properly
}
m.addAttribute(JsonEntityView.ENTITY, ids);
return JsonEntityView.VIEWNAME;
}
private ResourceSet parseResourceSet(String jsonString) {
try {
JsonElement el = parser.parse(jsonString);
if (el.isJsonObject()) {
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, "scopes"));
rs.setUri(getAsString(o, "uri"));
return rs;
}
return null;
} catch (JsonParseException e) {
return null;
}
}
/**
*
* Make sure the resource set doesn't have any restricted or reserved scopes.
*
* @param rs
*/
private ResourceSet validateScopes(ResourceSet rs) {
// scopes that the client is asking for
Set<SystemScope> requestedScopes = scopeService.fromStrings(rs.getScopes());
// the scopes that the resource set can have must be a subset of the dynamically allowed scopes
Set<SystemScope> allowedScopes = scopeService.removeRestrictedAndReservedScopes(requestedScopes);
rs.setScopes(scopeService.toStrings(allowedScopes));
return rs;
}
}