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}