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 *******************************************************************************/
016package org.mitre.uma.web;
017
018
019import static org.mitre.oauth2.web.AuthenticationUtilities.ensureOAuthScope;
020import static org.mitre.util.JsonUtils.getAsLong;
021import static org.mitre.util.JsonUtils.getAsString;
022import static org.mitre.util.JsonUtils.getAsStringSet;
023
024import java.util.Collection;
025import java.util.Collections;
026import java.util.HashSet;
027import java.util.Set;
028
029import org.mitre.oauth2.model.SystemScope;
030import org.mitre.oauth2.service.SystemScopeService;
031import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
032import org.mitre.openid.connect.view.HttpCodeView;
033import org.mitre.openid.connect.view.JsonEntityView;
034import org.mitre.openid.connect.view.JsonErrorView;
035import org.mitre.uma.model.ResourceSet;
036import org.mitre.uma.service.ResourceSetService;
037import org.mitre.uma.view.ResourceSetEntityAbbreviatedView;
038import org.mitre.uma.view.ResourceSetEntityView;
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041import org.springframework.beans.factory.annotation.Autowired;
042import org.springframework.http.HttpStatus;
043import org.springframework.security.access.prepost.PreAuthorize;
044import org.springframework.security.core.Authentication;
045import org.springframework.security.oauth2.provider.OAuth2Authentication;
046import org.springframework.stereotype.Controller;
047import org.springframework.ui.Model;
048import org.springframework.util.MimeTypeUtils;
049import org.springframework.web.bind.annotation.PathVariable;
050import org.springframework.web.bind.annotation.RequestBody;
051import org.springframework.web.bind.annotation.RequestMapping;
052import org.springframework.web.bind.annotation.RequestMethod;
053
054import com.google.common.base.Strings;
055import com.google.gson.JsonElement;
056import com.google.gson.JsonObject;
057import com.google.gson.JsonParseException;
058import com.google.gson.JsonParser;
059
060@Controller
061@RequestMapping("/" + ResourceSetRegistrationEndpoint.URL)
062@PreAuthorize("hasRole('ROLE_USER')")
063public class ResourceSetRegistrationEndpoint {
064
065        private static final Logger logger = LoggerFactory.getLogger(ResourceSetRegistrationEndpoint.class);
066
067        public static final String DISCOVERY_URL = "resource_set";
068        public static final String URL = DISCOVERY_URL + "/resource_set";
069
070        @Autowired
071        private ResourceSetService resourceSetService;
072
073        @Autowired
074        private ConfigurationPropertiesBean config;
075
076        @Autowired
077        private SystemScopeService scopeService;
078
079        private JsonParser parser = new JsonParser();
080
081        @RequestMapping(method = RequestMethod.POST, produces = MimeTypeUtils.APPLICATION_JSON_VALUE, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE)
082        public String createResourceSet(@RequestBody String jsonString, Model m, Authentication auth) {
083                ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE);
084
085                ResourceSet rs = parseResourceSet(jsonString);
086
087                if (rs == null) { // there was no resource set in the body
088                        logger.warn("Resource set registration missing body.");
089
090                        m.addAttribute("code", HttpStatus.BAD_REQUEST);
091                        m.addAttribute("error_description", "Resource request was missing body.");
092                        return JsonErrorView.VIEWNAME;
093                }
094
095                if (auth instanceof OAuth2Authentication) {
096                        // if it's an OAuth mediated call, it's on behalf of a client, so store that
097                        OAuth2Authentication o2a = (OAuth2Authentication) auth;
098                        rs.setClientId(o2a.getOAuth2Request().getClientId());
099                        rs.setOwner(auth.getName()); // the username is going to be in the auth object
100                } else {
101                        // this one shouldn't be called if it's not OAuth
102                        m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
103                        m.addAttribute(JsonErrorView.ERROR_MESSAGE, "This call must be made with an OAuth token");
104                        return JsonErrorView.VIEWNAME;
105                }
106
107                rs = validateScopes(rs);
108
109                if (Strings.isNullOrEmpty(rs.getName()) // there was no name (required)
110                                || rs.getScopes() == null // there were no scopes (required)
111                                ) {
112
113                        logger.warn("Resource set registration missing one or more required fields.");
114
115                        m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
116                        m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Resource request was missing one or more required fields.");
117                        return JsonErrorView.VIEWNAME;
118                }
119
120                ResourceSet saved = resourceSetService.saveNew(rs);
121
122                m.addAttribute(HttpCodeView.CODE, HttpStatus.CREATED);
123                m.addAttribute(JsonEntityView.ENTITY, saved);
124                m.addAttribute(ResourceSetEntityAbbreviatedView.LOCATION, config.getIssuer() + URL + "/" + saved.getId());
125
126                return ResourceSetEntityAbbreviatedView.VIEWNAME;
127
128        }
129
130        @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
131        public String readResourceSet(@PathVariable ("id") Long id, Model m, Authentication auth) {
132                ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE);
133
134                ResourceSet rs = resourceSetService.getById(id);
135
136                if (rs == null) {
137                        m.addAttribute("code", HttpStatus.NOT_FOUND);
138                        m.addAttribute("error", "not_found");
139                        return JsonErrorView.VIEWNAME;
140                } else {
141
142                        rs = validateScopes(rs);
143
144                        if (!auth.getName().equals(rs.getOwner())) {
145
146                                logger.warn("Unauthorized resource set request from wrong user; expected " + rs.getOwner() + " got " + auth.getName());
147
148                                // it wasn't issued to this user
149                                m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN);
150                                return JsonErrorView.VIEWNAME;
151                        } else {
152                                m.addAttribute(JsonEntityView.ENTITY, rs);
153                                return ResourceSetEntityView.VIEWNAME;
154                        }
155
156                }
157
158        }
159
160        @RequestMapping(value = "/{id}", method = RequestMethod.PUT, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
161        public String updateResourceSet(@PathVariable ("id") Long id, @RequestBody String jsonString, Model m, Authentication auth) {
162                ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE);
163
164                ResourceSet newRs = parseResourceSet(jsonString);
165
166                if (newRs == null // there was no resource set in the body
167                                || Strings.isNullOrEmpty(newRs.getName()) // there was no name (required)
168                                || newRs.getScopes() == null // there were no scopes (required)
169                                || newRs.getId() == null || !newRs.getId().equals(id) // the IDs didn't match
170                                ) {
171
172                        logger.warn("Resource set registration missing one or more required fields.");
173
174                        m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
175                        m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Resource request was missing one or more required fields.");
176                        return JsonErrorView.VIEWNAME;
177                }
178
179                ResourceSet rs = resourceSetService.getById(id);
180
181                if (rs == null) {
182                        m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND);
183                        m.addAttribute(JsonErrorView.ERROR, "not_found");
184                        return JsonErrorView.VIEWNAME;
185                } else {
186                        if (!auth.getName().equals(rs.getOwner())) {
187
188                                logger.warn("Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName());
189
190                                // it wasn't issued to this user
191                                m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN);
192                                return JsonErrorView.VIEWNAME;
193                        } else {
194
195                                ResourceSet saved = resourceSetService.update(rs, newRs);
196
197                                m.addAttribute(JsonEntityView.ENTITY, saved);
198                                m.addAttribute(ResourceSetEntityAbbreviatedView.LOCATION, config.getIssuer() + URL + "/" + rs.getId());
199                                return ResourceSetEntityAbbreviatedView.VIEWNAME;
200                        }
201
202                }
203        }
204
205        @RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
206        public String deleteResourceSet(@PathVariable ("id") Long id, Model m, Authentication auth) {
207                ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE);
208
209                ResourceSet rs = resourceSetService.getById(id);
210
211                if (rs == null) {
212                        m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND);
213                        m.addAttribute(JsonErrorView.ERROR, "not_found");
214                        return JsonErrorView.VIEWNAME;
215                } else {
216                        if (!auth.getName().equals(rs.getOwner())) {
217
218                                logger.warn("Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName());
219
220                                // it wasn't issued to this user
221                                m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN);
222                                return JsonErrorView.VIEWNAME;
223                        } else if (auth instanceof OAuth2Authentication &&
224                                        !((OAuth2Authentication)auth).getOAuth2Request().getClientId().equals(rs.getClientId())){
225
226                                logger.warn("Unauthorized resource set request from bad client; expected " + rs.getClientId() + " got " + ((OAuth2Authentication)auth).getOAuth2Request().getClientId());
227
228                                // it wasn't issued to this client
229                                m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN);
230                                return JsonErrorView.VIEWNAME;
231                        } else {
232
233                                // user and client matched
234                                resourceSetService.remove(rs);
235
236                                m.addAttribute(HttpCodeView.CODE, HttpStatus.NO_CONTENT);
237                                return HttpCodeView.VIEWNAME;
238                        }
239
240                }
241        }
242
243        @RequestMapping(method = RequestMethod.GET, produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
244        public String listResourceSets(Model m, Authentication auth) {
245                ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE);
246
247                String owner = auth.getName();
248
249                Collection<ResourceSet> resourceSets = Collections.emptySet();
250                if (auth instanceof OAuth2Authentication) {
251                        // if it's an OAuth mediated call, it's on behalf of a client, so look that up too
252                        OAuth2Authentication o2a = (OAuth2Authentication) auth;
253                        resourceSets = resourceSetService.getAllForOwnerAndClient(owner, o2a.getOAuth2Request().getClientId());
254                } else {
255                        // otherwise get everything for the current user
256                        resourceSets = resourceSetService.getAllForOwner(owner);
257                }
258
259                // build the entity here and send to the display
260
261                Set<String> ids = new HashSet<>();
262                for (ResourceSet resourceSet : resourceSets) {
263                        ids.add(resourceSet.getId().toString()); // add them all as strings so that gson renders them properly
264                }
265
266                m.addAttribute(JsonEntityView.ENTITY, ids);
267                return JsonEntityView.VIEWNAME;
268        }
269
270        private ResourceSet parseResourceSet(String jsonString) {
271
272                try {
273                        JsonElement el = parser.parse(jsonString);
274
275                        if (el.isJsonObject()) {
276                                JsonObject o = el.getAsJsonObject();
277
278                                ResourceSet rs = new ResourceSet();
279                                rs.setId(getAsLong(o, "_id"));
280                                rs.setName(getAsString(o, "name"));
281                                rs.setIconUri(getAsString(o, "icon_uri"));
282                                rs.setType(getAsString(o, "type"));
283                                rs.setScopes(getAsStringSet(o, "scopes"));
284                                rs.setUri(getAsString(o, "uri"));
285
286                                return rs;
287
288                        }
289
290                        return null;
291
292                } catch (JsonParseException e) {
293                        return null;
294                }
295
296        }
297
298
299        /**
300         *
301         * Make sure the resource set doesn't have any restricted or reserved scopes.
302         *
303         * @param rs
304         */
305        private ResourceSet validateScopes(ResourceSet rs) {
306                // scopes that the client is asking for
307                Set<SystemScope> requestedScopes = scopeService.fromStrings(rs.getScopes());
308
309                // the scopes that the resource set can have must be a subset of the dynamically allowed scopes
310                Set<SystemScope> allowedScopes = scopeService.removeRestrictedAndReservedScopes(requestedScopes);
311
312                rs.setScopes(scopeService.toStrings(allowedScopes));
313
314                return rs;
315        }
316
317}