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.Map; 020 021import org.mitre.oauth2.model.OAuth2AccessTokenEntity; 022import org.mitre.oauth2.service.OAuth2TokenEntityService; 023import org.mitre.oauth2.service.SystemScopeService; 024import org.mitre.oauth2.web.AuthenticationUtilities; 025import org.mitre.openid.connect.view.HttpCodeView; 026import org.mitre.openid.connect.view.JsonEntityView; 027import org.mitre.openid.connect.view.JsonErrorView; 028import org.mitre.uma.model.Claim; 029import org.mitre.uma.model.ClaimProcessingResult; 030import org.mitre.uma.model.PermissionTicket; 031import org.mitre.uma.model.ResourceSet; 032import org.mitre.uma.service.ClaimsProcessingService; 033import org.mitre.uma.service.PermissionService; 034import org.mitre.uma.service.UmaTokenService; 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037import org.springframework.beans.factory.annotation.Autowired; 038import org.springframework.http.HttpStatus; 039import org.springframework.security.core.Authentication; 040import org.springframework.security.oauth2.provider.OAuth2Authentication; 041import org.springframework.stereotype.Controller; 042import org.springframework.ui.Model; 043import org.springframework.util.MimeTypeUtils; 044import org.springframework.web.bind.annotation.RequestBody; 045import org.springframework.web.bind.annotation.RequestMapping; 046import org.springframework.web.bind.annotation.RequestMethod; 047 048import com.google.common.collect.ImmutableMap; 049import com.google.gson.JsonArray; 050import com.google.gson.JsonElement; 051import com.google.gson.JsonObject; 052import com.google.gson.JsonParser; 053import com.google.gson.JsonPrimitive; 054 055/** 056 * @author jricher 057 * 058 */ 059@Controller 060@RequestMapping("/" + AuthorizationRequestEndpoint.URL) 061public class AuthorizationRequestEndpoint { 062 // Logger for this class 063 private static final Logger logger = LoggerFactory.getLogger(AuthorizationRequestEndpoint.class); 064 065 public static final String RPT = "rpt"; 066 public static final String TICKET = "ticket"; 067 public static final String URL = "authz_request"; 068 069 @Autowired 070 private PermissionService permissionService; 071 072 @Autowired 073 private OAuth2TokenEntityService tokenService; 074 075 @Autowired 076 private ClaimsProcessingService claimsProcessingService; 077 078 @Autowired 079 private UmaTokenService umaTokenService; 080 081 @RequestMapping(method = RequestMethod.POST, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) 082 public String authorizationRequest(@RequestBody String jsonString, Model m, Authentication auth) { 083 084 AuthenticationUtilities.ensureOAuthScope(auth, SystemScopeService.UMA_AUTHORIZATION_SCOPE); 085 086 JsonParser parser = new JsonParser(); 087 JsonElement e = parser.parse(jsonString); 088 089 if (e.isJsonObject()) { 090 JsonObject o = e.getAsJsonObject(); 091 092 if (o.has(TICKET)) { 093 094 OAuth2AccessTokenEntity incomingRpt = null; 095 if (o.has(RPT)) { 096 String rptValue = o.get(RPT).getAsString(); 097 incomingRpt = tokenService.readAccessToken(rptValue); 098 } 099 100 String ticketValue = o.get(TICKET).getAsString(); 101 102 PermissionTicket ticket = permissionService.getByTicket(ticketValue); 103 104 if (ticket != null) { 105 // found the ticket, see if it's any good 106 107 ResourceSet rs = ticket.getPermission().getResourceSet(); 108 109 if (rs.getPolicies() == null || rs.getPolicies().isEmpty()) { 110 // the required claims are empty, this resource has no way to be authorized 111 112 m.addAttribute(JsonErrorView.ERROR, "not_authorized"); 113 m.addAttribute(JsonErrorView.ERROR_MESSAGE, "This resource set can not be accessed."); 114 m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); 115 return JsonErrorView.VIEWNAME; 116 } else { 117 // claims weren't empty or missing, we need to check against what we have 118 119 ClaimProcessingResult result = claimsProcessingService.claimsAreSatisfied(rs, ticket); 120 121 122 if (result.isSatisfied()) { 123 // the service found what it was looking for, issue a token 124 125 // we need to downscope this based on the required set that was matched if it was matched 126 OAuth2Authentication o2auth = (OAuth2Authentication) auth; 127 128 OAuth2AccessTokenEntity token = umaTokenService.createRequestingPartyToken(o2auth, ticket, result.getMatched()); 129 130 // if we have an inbound RPT, throw it out because we're replacing it 131 if (incomingRpt != null) { 132 tokenService.revokeAccessToken(incomingRpt); 133 } 134 135 Map<String, String> entity = ImmutableMap.of("rpt", token.getValue()); 136 137 m.addAttribute(JsonEntityView.ENTITY, entity); 138 139 return JsonEntityView.VIEWNAME; 140 141 } else { 142 143 // if we got here, the claim didn't match, forward the user to the claim gathering endpoint 144 JsonObject entity = new JsonObject(); 145 146 entity.addProperty(JsonErrorView.ERROR, "need_info"); 147 JsonObject details = new JsonObject(); 148 149 JsonObject rpClaims = new JsonObject(); 150 rpClaims.addProperty("redirect_user", true); 151 rpClaims.addProperty("ticket", ticketValue); 152 JsonArray req = new JsonArray(); 153 for (Claim claim : result.getUnmatched()) { 154 JsonObject c = new JsonObject(); 155 c.addProperty("name", claim.getName()); 156 c.addProperty("friendly_name", claim.getFriendlyName()); 157 c.addProperty("claim_type", claim.getClaimType()); 158 JsonArray f = new JsonArray(); 159 for (String format : claim.getClaimTokenFormat()) { 160 f.add(new JsonPrimitive(format)); 161 } 162 c.add("claim_token_format", f); 163 JsonArray i = new JsonArray(); 164 for (String issuer : claim.getIssuer()) { 165 i.add(new JsonPrimitive(issuer)); 166 } 167 c.add("issuer", i); 168 req.add(c); 169 } 170 rpClaims.add("required_claims", req); 171 details.add("requesting_party_claims", rpClaims); 172 entity.add("error_details", details); 173 174 m.addAttribute(JsonEntityView.ENTITY, entity); 175 return JsonEntityView.VIEWNAME; 176 } 177 178 179 } 180 } else { 181 // ticket wasn't found, return an error 182 m.addAttribute(HttpStatus.BAD_REQUEST); 183 m.addAttribute(JsonErrorView.ERROR, "invalid_ticket"); 184 return JsonErrorView.VIEWNAME; 185 } 186 187 } else { 188 m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); 189 m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Missing JSON elements."); 190 return JsonErrorView.VIEWNAME; 191 } 192 193 194 } else { 195 m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); 196 m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Malformed JSON request."); 197 return JsonErrorView.VIEWNAME; 198 } 199 200 } 201 202}