001/******************************************************************************* 002 * Copyright 2017 The MIT Internet Trust Consortium 003 * 004 * Portions copyright 2011-2013 The MITRE Corporation 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 *******************************************************************************/ 018/** 019 * 020 */ 021package org.mitre.oauth2.web; 022 023import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT; 024import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_SEPARATOR; 025 026import java.net.URISyntaxException; 027import java.security.Principal; 028import java.util.Date; 029import java.util.HashMap; 030import java.util.LinkedHashSet; 031import java.util.List; 032import java.util.Map; 033import java.util.Set; 034 035import org.apache.http.client.utils.URIBuilder; 036import org.mitre.oauth2.model.ClientDetailsEntity; 037import org.mitre.oauth2.model.SystemScope; 038import org.mitre.oauth2.service.ClientDetailsEntityService; 039import org.mitre.oauth2.service.SystemScopeService; 040import org.mitre.openid.connect.model.UserInfo; 041import org.mitre.openid.connect.service.ScopeClaimTranslationService; 042import org.mitre.openid.connect.service.StatsService; 043import org.mitre.openid.connect.service.UserInfoService; 044import org.mitre.openid.connect.view.HttpCodeView; 045import org.slf4j.Logger; 046import org.slf4j.LoggerFactory; 047import org.springframework.beans.factory.annotation.Autowired; 048import org.springframework.http.HttpStatus; 049import org.springframework.security.access.prepost.PreAuthorize; 050import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; 051import org.springframework.security.oauth2.provider.AuthorizationRequest; 052import org.springframework.security.oauth2.provider.endpoint.RedirectResolver; 053import org.springframework.stereotype.Controller; 054import org.springframework.web.bind.annotation.ModelAttribute; 055import org.springframework.web.bind.annotation.RequestMapping; 056import org.springframework.web.bind.annotation.SessionAttributes; 057 058import com.google.common.base.Joiner; 059import com.google.common.base.Splitter; 060import com.google.common.base.Strings; 061import com.google.common.collect.Sets; 062import com.google.gson.JsonObject; 063 064/** 065 * @author jricher 066 * 067 */ 068@Controller 069@SessionAttributes("authorizationRequest") 070public class OAuthConfirmationController { 071 072 073 @Autowired 074 private ClientDetailsEntityService clientService; 075 076 @Autowired 077 private SystemScopeService scopeService; 078 079 @Autowired 080 private ScopeClaimTranslationService scopeClaimTranslationService; 081 082 @Autowired 083 private UserInfoService userInfoService; 084 085 @Autowired 086 private StatsService statsService; 087 088 @Autowired 089 private RedirectResolver redirectResolver; 090 091 /** 092 * Logger for this class 093 */ 094 private static final Logger logger = LoggerFactory.getLogger(OAuthConfirmationController.class); 095 096 public OAuthConfirmationController() { 097 098 } 099 100 public OAuthConfirmationController(ClientDetailsEntityService clientService) { 101 this.clientService = clientService; 102 } 103 104 @PreAuthorize("hasRole('ROLE_USER')") 105 @RequestMapping("/oauth/confirm_access") 106 public String confimAccess(Map<String, Object> model, @ModelAttribute("authorizationRequest") AuthorizationRequest authRequest, 107 Principal p) { 108 109 // Check the "prompt" parameter to see if we need to do special processing 110 111 String prompt = (String)authRequest.getExtensions().get(PROMPT); 112 List<String> prompts = Splitter.on(PROMPT_SEPARATOR).splitToList(Strings.nullToEmpty(prompt)); 113 ClientDetailsEntity client = null; 114 115 try { 116 client = clientService.loadClientByClientId(authRequest.getClientId()); 117 } catch (OAuth2Exception e) { 118 logger.error("confirmAccess: OAuth2Exception was thrown when attempting to load client", e); 119 model.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); 120 return HttpCodeView.VIEWNAME; 121 } catch (IllegalArgumentException e) { 122 logger.error("confirmAccess: IllegalArgumentException was thrown when attempting to load client", e); 123 model.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); 124 return HttpCodeView.VIEWNAME; 125 } 126 127 if (client == null) { 128 logger.error("confirmAccess: could not find client " + authRequest.getClientId()); 129 model.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND); 130 return HttpCodeView.VIEWNAME; 131 } 132 133 if (prompts.contains("none")) { 134 // if we've got a redirect URI then we'll send it 135 136 String url = redirectResolver.resolveRedirect(authRequest.getRedirectUri(), client); 137 138 try { 139 URIBuilder uriBuilder = new URIBuilder(url); 140 141 uriBuilder.addParameter("error", "interaction_required"); 142 if (!Strings.isNullOrEmpty(authRequest.getState())) { 143 uriBuilder.addParameter("state", authRequest.getState()); // copy the state parameter if one was given 144 } 145 146 return "redirect:" + uriBuilder.toString(); 147 148 } catch (URISyntaxException e) { 149 logger.error("Can't build redirect URI for prompt=none, sending error instead", e); 150 model.put("code", HttpStatus.FORBIDDEN); 151 return HttpCodeView.VIEWNAME; 152 } 153 } 154 155 model.put("auth_request", authRequest); 156 model.put("client", client); 157 158 String redirect_uri = authRequest.getRedirectUri(); 159 160 model.put("redirect_uri", redirect_uri); 161 162 163 // pre-process the scopes 164 Set<SystemScope> scopes = scopeService.fromStrings(authRequest.getScope()); 165 166 Set<SystemScope> sortedScopes = new LinkedHashSet<>(scopes.size()); 167 Set<SystemScope> systemScopes = scopeService.getAll(); 168 169 // sort scopes for display based on the inherent order of system scopes 170 for (SystemScope s : systemScopes) { 171 if (scopes.contains(s)) { 172 sortedScopes.add(s); 173 } 174 } 175 176 // add in any scopes that aren't system scopes to the end of the list 177 sortedScopes.addAll(Sets.difference(scopes, systemScopes)); 178 179 model.put("scopes", sortedScopes); 180 181 // get the userinfo claims for each scope 182 UserInfo user = userInfoService.getByUsername(p.getName()); 183 Map<String, Map<String, String>> claimsForScopes = new HashMap<>(); 184 if (user != null) { 185 JsonObject userJson = user.toJson(); 186 187 for (SystemScope systemScope : sortedScopes) { 188 Map<String, String> claimValues = new HashMap<>(); 189 190 Set<String> claims = scopeClaimTranslationService.getClaimsForScope(systemScope.getValue()); 191 for (String claim : claims) { 192 if (userJson.has(claim) && userJson.get(claim).isJsonPrimitive()) { 193 // TODO: this skips the address claim 194 claimValues.put(claim, userJson.get(claim).getAsString()); 195 } 196 } 197 198 claimsForScopes.put(systemScope.getValue(), claimValues); 199 } 200 } 201 202 model.put("claims", claimsForScopes); 203 204 // client stats 205 Integer count = statsService.getCountForClientId(client.getClientId()).getApprovedSiteCount(); 206 model.put("count", count); 207 208 209 // contacts 210 if (client.getContacts() != null) { 211 String contacts = Joiner.on(", ").join(client.getContacts()); 212 model.put("contacts", contacts); 213 } 214 215 // if the client is over a week old and has more than one registration, don't give such a big warning 216 // instead, tag as "Generally Recognized As Safe" (gras) 217 Date lastWeek = new Date(System.currentTimeMillis() - (60 * 60 * 24 * 7 * 1000)); 218 if (count > 1 && client.getCreatedAt() != null && client.getCreatedAt().before(lastWeek)) { 219 model.put("gras", true); 220 } else { 221 model.put("gras", false); 222 } 223 224 return "approve"; 225 } 226 227 /** 228 * @return the clientService 229 */ 230 public ClientDetailsEntityService getClientService() { 231 return clientService; 232 } 233 234 /** 235 * @param clientService the clientService to set 236 */ 237 public void setClientService(ClientDetailsEntityService clientService) { 238 this.clientService = clientService; 239 } 240 241 242}