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}