001/******************************************************************************* 002 * Copyright 2017 The MIT Internet Trust Consortium 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 *******************************************************************************/ 016 017package org.mitre.uma.web; 018 019import java.util.Collection; 020import java.util.HashSet; 021import java.util.Set; 022 023import org.mitre.openid.connect.view.HttpCodeView; 024import org.mitre.openid.connect.view.JsonEntityView; 025import org.mitre.openid.connect.view.JsonErrorView; 026import org.mitre.openid.connect.web.RootController; 027import org.mitre.uma.model.Claim; 028import org.mitre.uma.model.Policy; 029import org.mitre.uma.model.ResourceSet; 030import org.mitre.uma.service.ResourceSetService; 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033import org.springframework.beans.factory.annotation.Autowired; 034import org.springframework.http.HttpStatus; 035import org.springframework.security.access.prepost.PreAuthorize; 036import org.springframework.security.core.Authentication; 037import org.springframework.stereotype.Controller; 038import org.springframework.ui.Model; 039import org.springframework.util.MimeTypeUtils; 040import org.springframework.web.bind.annotation.PathVariable; 041import org.springframework.web.bind.annotation.RequestBody; 042import org.springframework.web.bind.annotation.RequestMapping; 043import org.springframework.web.bind.annotation.RequestMethod; 044 045import com.google.common.collect.Sets; 046import com.google.gson.Gson; 047 048/** 049 * API for managing policies on resource sets. 050 * 051 * @author jricher 052 * 053 */ 054@Controller 055@RequestMapping("/" + PolicyAPI.URL) 056@PreAuthorize("hasRole('ROLE_USER')") 057public class PolicyAPI { 058 059 // Logger for this class 060 private static final Logger logger = LoggerFactory.getLogger(PolicyAPI.class); 061 062 public static final String URL = RootController.API_URL + "/resourceset"; 063 public static final String POLICYURL = "/policy"; 064 065 private Gson gson = new Gson(); 066 067 @Autowired 068 private ResourceSetService resourceSetService; 069 070 /** 071 * List all resource sets for the current user 072 * @param m 073 * @param auth 074 * @return 075 */ 076 @RequestMapping(value = "", method = RequestMethod.GET, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) 077 public String getResourceSetsForCurrentUser(Model m, Authentication auth) { 078 079 Collection<ResourceSet> resourceSets = resourceSetService.getAllForOwner(auth.getName()); 080 081 m.addAttribute(JsonEntityView.ENTITY, resourceSets); 082 083 return JsonEntityView.VIEWNAME; 084 } 085 086 /** 087 * Get the indicated resource set 088 * @param rsid 089 * @param m 090 * @param auth 091 * @return 092 */ 093 @RequestMapping(value = "/{rsid}", method = RequestMethod.GET, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) 094 public String getResourceSet(@PathVariable (value = "rsid") Long rsid, Model m, Authentication auth) { 095 096 ResourceSet rs = resourceSetService.getById(rsid); 097 098 if (rs == null) { 099 m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); 100 return HttpCodeView.VIEWNAME; 101 } 102 103 if (!rs.getOwner().equals(auth.getName())) { 104 logger.warn("Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName()); 105 106 // authenticated user didn't match the owner of the resource set 107 m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); 108 return HttpCodeView.VIEWNAME; 109 } 110 111 m.addAttribute(JsonEntityView.ENTITY, rs); 112 113 return JsonEntityView.VIEWNAME; 114 } 115 116 /** 117 * Delete the indicated resource set 118 * @param rsid 119 * @param m 120 * @param auth 121 * @return 122 */ 123 @RequestMapping(value = "/{rsid}", method = RequestMethod.DELETE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) 124 public String deleteResourceSet(@PathVariable (value = "rsid") Long rsid, Model m, Authentication auth) { 125 126 ResourceSet rs = resourceSetService.getById(rsid); 127 128 if (rs == null) { 129 m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); 130 return HttpCodeView.VIEWNAME; 131 } 132 133 if (!rs.getOwner().equals(auth.getName())) { 134 logger.warn("Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName()); 135 136 // authenticated user didn't match the owner of the resource set 137 m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); 138 return HttpCodeView.VIEWNAME; 139 } 140 141 resourceSetService.remove(rs); 142 m.addAttribute(HttpCodeView.CODE, HttpStatus.NO_CONTENT); 143 return HttpCodeView.VIEWNAME; 144 145 } 146 147 /** 148 * List all the policies for the given resource set 149 * @param rsid 150 * @param m 151 * @param auth 152 * @return 153 */ 154 @RequestMapping(value = "/{rsid}" + POLICYURL, method = RequestMethod.GET, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) 155 public String getPoliciesForResourceSet(@PathVariable (value = "rsid") Long rsid, Model m, Authentication auth) { 156 157 ResourceSet rs = resourceSetService.getById(rsid); 158 159 if (rs == null) { 160 m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); 161 return HttpCodeView.VIEWNAME; 162 } 163 164 if (!rs.getOwner().equals(auth.getName())) { 165 logger.warn("Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName()); 166 167 // authenticated user didn't match the owner of the resource set 168 m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); 169 return HttpCodeView.VIEWNAME; 170 } 171 172 m.addAttribute(JsonEntityView.ENTITY, rs.getPolicies()); 173 174 return JsonEntityView.VIEWNAME; 175 } 176 177 /** 178 * Create a new policy on the given resource set 179 * @param rsid 180 * @param m 181 * @param auth 182 * @return 183 */ 184 @RequestMapping(value = "/{rsid}" + POLICYURL, method = RequestMethod.POST, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) 185 public String createNewPolicyForResourceSet(@PathVariable (value = "rsid") Long rsid, @RequestBody String jsonString, Model m, Authentication auth) { 186 ResourceSet rs = resourceSetService.getById(rsid); 187 188 if (rs == null) { 189 m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); 190 return HttpCodeView.VIEWNAME; 191 } 192 193 if (!rs.getOwner().equals(auth.getName())) { 194 logger.warn("Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName()); 195 196 // authenticated user didn't match the owner of the resource set 197 m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); 198 return HttpCodeView.VIEWNAME; 199 } 200 201 Policy p = gson.fromJson(jsonString, Policy.class); 202 203 if (p.getId() != null) { 204 logger.warn("Tried to add a policy with a non-null ID: " + p.getId()); 205 m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); 206 return HttpCodeView.VIEWNAME; 207 } 208 209 for (Claim claim : p.getClaimsRequired()) { 210 if (claim.getId() != null) { 211 logger.warn("Tried to add a policy with a non-null claim ID: " + claim.getId()); 212 m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); 213 return HttpCodeView.VIEWNAME; 214 } 215 } 216 217 rs.getPolicies().add(p); 218 ResourceSet saved = resourceSetService.update(rs, rs); 219 220 // find the new policy object 221 Collection<Policy> newPolicies = Sets.difference(new HashSet<>(saved.getPolicies()), new HashSet<>(rs.getPolicies())); 222 223 if (newPolicies.size() == 1) { 224 Policy newPolicy = newPolicies.iterator().next(); 225 m.addAttribute(JsonEntityView.ENTITY, newPolicy); 226 return JsonEntityView.VIEWNAME; 227 } else { 228 logger.warn("Unexpected result trying to add a new policy object: " + newPolicies); 229 m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR); 230 return HttpCodeView.VIEWNAME; 231 } 232 233 } 234 235 /** 236 * Get a specific policy 237 * @param rsid 238 * @param pid 239 * @param m 240 * @param auth 241 * @return 242 */ 243 @RequestMapping(value = "/{rsid}" + POLICYURL + "/{pid}", method = RequestMethod.GET, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) 244 public String getPolicy(@PathVariable (value = "rsid") Long rsid, @PathVariable (value = "pid") Long pid, Model m, Authentication auth) { 245 246 ResourceSet rs = resourceSetService.getById(rsid); 247 248 if (rs == null) { 249 m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); 250 return HttpCodeView.VIEWNAME; 251 } 252 253 if (!rs.getOwner().equals(auth.getName())) { 254 logger.warn("Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName()); 255 256 // authenticated user didn't match the owner of the resource set 257 m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); 258 return HttpCodeView.VIEWNAME; 259 } 260 261 for (Policy policy : rs.getPolicies()) { 262 if (policy.getId().equals(pid)) { 263 // found it! 264 m.addAttribute(JsonEntityView.ENTITY, policy); 265 return JsonEntityView.VIEWNAME; 266 } 267 } 268 269 // if we made it this far, we haven't found it 270 m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); 271 return HttpCodeView.VIEWNAME; 272 } 273 274 /** 275 * Update a specific policy 276 * @param rsid 277 * @param pid 278 * @param jsonString 279 * @param m 280 * @param auth 281 * @return 282 */ 283 @RequestMapping(value = "/{rsid}" + POLICYURL + "/{pid}", method = RequestMethod.PUT, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) 284 public String setClaimsForResourceSet(@PathVariable (value = "rsid") Long rsid, @PathVariable (value = "pid") Long pid, @RequestBody String jsonString, Model m, Authentication auth) { 285 286 ResourceSet rs = resourceSetService.getById(rsid); 287 288 if (rs == null) { 289 m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); 290 return HttpCodeView.VIEWNAME; 291 } 292 293 if (!rs.getOwner().equals(auth.getName())) { 294 logger.warn("Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName()); 295 296 // authenticated user didn't match the owner of the resource set 297 m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); 298 return HttpCodeView.VIEWNAME; 299 } 300 301 Policy p = gson.fromJson(jsonString, Policy.class); 302 303 if (!pid.equals(p.getId())) { 304 logger.warn("Policy ID mismatch, expected " + pid + " got " + p.getId()); 305 306 m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); 307 return HttpCodeView.VIEWNAME; 308 } 309 310 for (Policy policy : rs.getPolicies()) { 311 if (policy.getId().equals(pid)) { 312 // found it! 313 314 // find the existing claim IDs, make sure we're not overwriting anything from another policy 315 Set<Long> claimIds = new HashSet<>(); 316 for (Claim claim : policy.getClaimsRequired()) { 317 claimIds.add(claim.getId()); 318 } 319 320 for (Claim claim : p.getClaimsRequired()) { 321 if (claim.getId() != null && !claimIds.contains(claim.getId())) { 322 logger.warn("Tried to add a policy with a an unmatched claim ID: got " + claim.getId() + " expected " + claimIds); 323 m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); 324 return HttpCodeView.VIEWNAME; 325 } 326 } 327 328 // update the existing object with the new values 329 policy.setClaimsRequired(p.getClaimsRequired()); 330 policy.setName(p.getName()); 331 policy.setScopes(p.getScopes()); 332 333 resourceSetService.update(rs, rs); 334 335 m.addAttribute(JsonEntityView.ENTITY, policy); 336 return JsonEntityView.VIEWNAME; 337 } 338 } 339 340 // if we made it this far, we haven't found it 341 m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); 342 return HttpCodeView.VIEWNAME; 343 } 344 345 /** 346 * Delete a specific policy 347 * @param rsid 348 * @param pid 349 * @param m 350 * @param auth 351 * @return 352 */ 353 @RequestMapping(value = "/{rsid}" + POLICYURL + "/{pid}", method = RequestMethod.DELETE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) 354 public String deleteResourceSet(@PathVariable ("rsid") Long rsid, @PathVariable (value = "pid") Long pid, Model m, Authentication auth) { 355 356 ResourceSet rs = resourceSetService.getById(rsid); 357 358 if (rs == null) { 359 m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); 360 m.addAttribute(JsonErrorView.ERROR, "not_found"); 361 return JsonErrorView.VIEWNAME; 362 } 363 364 if (!auth.getName().equals(rs.getOwner())) { 365 366 logger.warn("Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName()); 367 368 // it wasn't issued to this user 369 m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); 370 return JsonErrorView.VIEWNAME; 371 } 372 373 374 for (Policy policy : rs.getPolicies()) { 375 if (policy.getId().equals(pid)) { 376 // found it! 377 rs.getPolicies().remove(policy); 378 resourceSetService.update(rs, rs); 379 380 m.addAttribute(HttpCodeView.CODE, HttpStatus.NO_CONTENT); 381 return HttpCodeView.VIEWNAME; 382 } 383 } 384 385 // if we made it this far, we haven't found it 386 m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); 387 return HttpCodeView.VIEWNAME; 388 389 } 390 391}