AuthorizationRequestEndpoint.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 java.util.Map;
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
import org.mitre.oauth2.service.OAuth2TokenEntityService;
import org.mitre.oauth2.service.SystemScopeService;
import org.mitre.oauth2.web.AuthenticationUtilities;
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.Claim;
import org.mitre.uma.model.ClaimProcessingResult;
import org.mitre.uma.model.PermissionTicket;
import org.mitre.uma.model.ResourceSet;
import org.mitre.uma.service.ClaimsProcessingService;
import org.mitre.uma.service.PermissionService;
import org.mitre.uma.service.UmaTokenService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
/**
 * @author jricher
 *
 */
@Controller
@RequestMapping("/" + AuthorizationRequestEndpoint.URL)
public class AuthorizationRequestEndpoint {
	// Logger for this class
	private static final Logger logger = LoggerFactory.getLogger(AuthorizationRequestEndpoint.class);
	public static final String RPT = "rpt";
	public static final String TICKET = "ticket";
	public static final String URL = "authz_request";
	@Autowired
	private PermissionService permissionService;
	@Autowired
	private OAuth2TokenEntityService tokenService;
	@Autowired
	private ClaimsProcessingService claimsProcessingService;
	@Autowired
	private UmaTokenService umaTokenService;
	@RequestMapping(method = RequestMethod.POST, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
	public String authorizationRequest(@RequestBody String jsonString, Model m, Authentication auth) {
		AuthenticationUtilities.ensureOAuthScope(auth, SystemScopeService.UMA_AUTHORIZATION_SCOPE);
		JsonParser parser = new JsonParser();
		JsonElement e = parser.parse(jsonString);
		if (e.isJsonObject()) {
			JsonObject o = e.getAsJsonObject();
			if (o.has(TICKET)) {
				OAuth2AccessTokenEntity incomingRpt = null;
				if (o.has(RPT)) {
					String rptValue = o.get(RPT).getAsString();
					incomingRpt = tokenService.readAccessToken(rptValue);
				}
				String ticketValue = o.get(TICKET).getAsString();
				PermissionTicket ticket = permissionService.getByTicket(ticketValue);
				if (ticket != null) {
					// found the ticket, see if it's any good
					ResourceSet rs = ticket.getPermission().getResourceSet();
					if (rs.getPolicies() == null || rs.getPolicies().isEmpty()) {
						// the required claims are empty, this resource has no way to be authorized
						m.addAttribute(JsonErrorView.ERROR, "not_authorized");
						m.addAttribute(JsonErrorView.ERROR_MESSAGE, "This resource set can not be accessed.");
						m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN);
						return JsonErrorView.VIEWNAME;
					} else {
						// claims weren't empty or missing, we need to check against what we have
						ClaimProcessingResult result = claimsProcessingService.claimsAreSatisfied(rs, ticket);
						if (result.isSatisfied()) {
							// the service found what it was looking for, issue a token
							// we need to downscope this based on the required set that was matched if it was matched
							OAuth2Authentication o2auth = (OAuth2Authentication) auth;
							OAuth2AccessTokenEntity token = umaTokenService.createRequestingPartyToken(o2auth, ticket, result.getMatched());
							// if we have an inbound RPT, throw it out because we're replacing it
							if (incomingRpt != null) {
								tokenService.revokeAccessToken(incomingRpt);
							}
							Map<String, String> entity = ImmutableMap.of("rpt", token.getValue());
							m.addAttribute(JsonEntityView.ENTITY, entity);
							return JsonEntityView.VIEWNAME;
						} else {
							// if we got here, the claim didn't match, forward the user to the claim gathering endpoint
							JsonObject entity = new JsonObject();
							entity.addProperty(JsonErrorView.ERROR, "need_info");
							JsonObject details = new JsonObject();
							JsonObject rpClaims = new JsonObject();
							rpClaims.addProperty("redirect_user", true);
							rpClaims.addProperty("ticket", ticketValue);
							JsonArray req = new JsonArray();
							for (Claim claim : result.getUnmatched()) {
								JsonObject c = new JsonObject();
								c.addProperty("name", claim.getName());
								c.addProperty("friendly_name", claim.getFriendlyName());
								c.addProperty("claim_type", claim.getClaimType());
								JsonArray f = new JsonArray();
								for (String format : claim.getClaimTokenFormat()) {
									f.add(new JsonPrimitive(format));
								}
								c.add("claim_token_format", f);
								JsonArray i = new JsonArray();
								for (String issuer : claim.getIssuer()) {
									i.add(new JsonPrimitive(issuer));
								}
								c.add("issuer", i);
								req.add(c);
							}
							rpClaims.add("required_claims", req);
							details.add("requesting_party_claims", rpClaims);
							entity.add("error_details", details);
							m.addAttribute(JsonEntityView.ENTITY, entity);
							return JsonEntityView.VIEWNAME;
						}
					}
				} else {
					// ticket wasn't found, return an error
					m.addAttribute(HttpStatus.BAD_REQUEST);
					m.addAttribute(JsonErrorView.ERROR, "invalid_ticket");
					return JsonErrorView.VIEWNAME;
				}
			} else {
				m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
				m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Missing JSON elements.");
				return JsonErrorView.VIEWNAME;
			}
		} else {
			m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
			m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Malformed JSON request.");
			return JsonErrorView.VIEWNAME;
		}
	}
}