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 *******************************************************************************/ 018package org.mitre.openid.connect.token; 019 020import static org.mitre.openid.connect.request.ConnectRequestParameters.APPROVED_SITE; 021import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT; 022import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_CONSENT; 023import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_SEPARATOR; 024 025import java.util.Calendar; 026import java.util.Collection; 027import java.util.Date; 028import java.util.HashMap; 029import java.util.List; 030import java.util.Map; 031import java.util.Set; 032 033import javax.servlet.http.HttpSession; 034 035import org.mitre.oauth2.service.SystemScopeService; 036import org.mitre.openid.connect.model.ApprovedSite; 037import org.mitre.openid.connect.model.WhitelistedSite; 038import org.mitre.openid.connect.service.ApprovedSiteService; 039import org.mitre.openid.connect.service.WhitelistedSiteService; 040import org.mitre.openid.connect.web.AuthenticationTimeStamper; 041import org.springframework.beans.factory.annotation.Autowired; 042import org.springframework.security.core.Authentication; 043import org.springframework.security.oauth2.provider.AuthorizationRequest; 044import org.springframework.security.oauth2.provider.ClientDetails; 045import org.springframework.security.oauth2.provider.ClientDetailsService; 046import org.springframework.security.oauth2.provider.approval.UserApprovalHandler; 047import org.springframework.stereotype.Component; 048import org.springframework.web.context.request.RequestContextHolder; 049import org.springframework.web.context.request.ServletRequestAttributes; 050 051import com.google.common.base.Splitter; 052import com.google.common.base.Strings; 053import com.google.common.collect.Sets; 054 055/** 056 * Custom User Approval Handler implementation which uses a concept of a whitelist, 057 * blacklist, and greylist. 058 * 059 * Blacklisted sites will be caught and handled before this 060 * point. 061 * 062 * Whitelisted sites will be automatically approved, and an ApprovedSite entry will 063 * be created for the site the first time a given user access it. 064 * 065 * All other sites fall into the greylist - the user will be presented with the user 066 * approval page upon their first visit 067 * @author aanganes 068 * 069 */ 070@Component("tofuUserApprovalHandler") 071public class TofuUserApprovalHandler implements UserApprovalHandler { 072 073 @Autowired 074 private ApprovedSiteService approvedSiteService; 075 076 @Autowired 077 private WhitelistedSiteService whitelistedSiteService; 078 079 @Autowired 080 private ClientDetailsService clientDetailsService; 081 082 @Autowired 083 private SystemScopeService systemScopes; 084 085 /** 086 * Check if the user has already stored a positive approval decision for this site; or if the 087 * site is whitelisted, approve it automatically. 088 * 089 * Otherwise, return false so that the user will see the approval page and can make their own decision. 090 * 091 * @param authorizationRequest the incoming authorization request 092 * @param userAuthentication the Principal representing the currently-logged-in user 093 * 094 * @return true if the site is approved, false otherwise 095 */ 096 @Override 097 public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { 098 099 // if this request is already approved, pass that info through 100 // (this flag may be set by updateBeforeApproval, which can also do funny things with scopes, etc) 101 if (authorizationRequest.isApproved()) { 102 return true; 103 } else { 104 // if not, check to see if the user has approved it 105 // TODO: make parameter name configurable? 106 return Boolean.parseBoolean(authorizationRequest.getApprovalParameters().get("user_oauth_approval")); 107 } 108 109 } 110 111 /** 112 * Check if the user has already stored a positive approval decision for this site; or if the 113 * site is whitelisted, approve it automatically. 114 * 115 * Otherwise the user will be directed to the approval page and can make their own decision. 116 * 117 * @param authorizationRequest the incoming authorization request 118 * @param userAuthentication the Principal representing the currently-logged-in user 119 * 120 * @return the updated AuthorizationRequest 121 */ 122 @Override 123 public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { 124 125 //First, check database to see if the user identified by the userAuthentication has stored an approval decision 126 127 String userId = userAuthentication.getName(); 128 String clientId = authorizationRequest.getClientId(); 129 130 //lookup ApprovedSites by userId and clientId 131 boolean alreadyApproved = false; 132 133 // find out if we're supposed to force a prompt on the user or not 134 String prompt = (String) authorizationRequest.getExtensions().get(PROMPT); 135 List<String> prompts = Splitter.on(PROMPT_SEPARATOR).splitToList(Strings.nullToEmpty(prompt)); 136 if (!prompts.contains(PROMPT_CONSENT)) { 137 // if the prompt parameter is set to "consent" then we can't use approved sites or whitelisted sites 138 // otherwise, we need to check them below 139 140 Collection<ApprovedSite> aps = approvedSiteService.getByClientIdAndUserId(clientId, userId); 141 for (ApprovedSite ap : aps) { 142 143 if (!ap.isExpired()) { 144 145 // if we find one that fits... 146 if (systemScopes.scopesMatch(ap.getAllowedScopes(), authorizationRequest.getScope())) { 147 148 //We have a match; update the access date on the AP entry and return true. 149 ap.setAccessDate(new Date()); 150 approvedSiteService.save(ap); 151 152 String apId = ap.getId().toString(); 153 authorizationRequest.getExtensions().put(APPROVED_SITE, apId); 154 authorizationRequest.setApproved(true); 155 alreadyApproved = true; 156 157 setAuthTime(authorizationRequest); 158 } 159 } 160 } 161 162 if (!alreadyApproved) { 163 WhitelistedSite ws = whitelistedSiteService.getByClientId(clientId); 164 if (ws != null && systemScopes.scopesMatch(ws.getAllowedScopes(), authorizationRequest.getScope())) { 165 authorizationRequest.setApproved(true); 166 167 setAuthTime(authorizationRequest); 168 } 169 } 170 } 171 172 return authorizationRequest; 173 174 } 175 176 177 @Override 178 public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { 179 180 String userId = userAuthentication.getName(); 181 String clientId = authorizationRequest.getClientId(); 182 ClientDetails client = clientDetailsService.loadClientByClientId(clientId); 183 184 // This must be re-parsed here because SECOAUTH forces us to call things in a strange order 185 if (Boolean.parseBoolean(authorizationRequest.getApprovalParameters().get("user_oauth_approval"))) { 186 187 authorizationRequest.setApproved(true); 188 189 // process scopes from user input 190 Set<String> allowedScopes = Sets.newHashSet(); 191 Map<String,String> approvalParams = authorizationRequest.getApprovalParameters(); 192 193 Set<String> keys = approvalParams.keySet(); 194 195 for (String key : keys) { 196 if (key.startsWith("scope_")) { 197 //This is a scope parameter from the approval page. The value sent back should 198 //be the scope string. Check to make sure it is contained in the client's 199 //registered allowed scopes. 200 201 String scope = approvalParams.get(key); 202 Set<String> approveSet = Sets.newHashSet(scope); 203 204 //Make sure this scope is allowed for the given client 205 if (systemScopes.scopesMatch(client.getScope(), approveSet)) { 206 207 allowedScopes.add(scope); 208 } 209 210 } 211 } 212 213 // inject the user-allowed scopes into the auth request 214 authorizationRequest.setScope(allowedScopes); 215 216 //Only store an ApprovedSite if the user has checked "remember this decision": 217 String remember = authorizationRequest.getApprovalParameters().get("remember"); 218 if (!Strings.isNullOrEmpty(remember) && !remember.equals("none")) { 219 220 Date timeout = null; 221 if (remember.equals("one-hour")) { 222 // set the timeout to one hour from now 223 Calendar cal = Calendar.getInstance(); 224 cal.add(Calendar.HOUR, 1); 225 timeout = cal.getTime(); 226 } 227 228 ApprovedSite newSite = approvedSiteService.createApprovedSite(clientId, userId, timeout, allowedScopes); 229 String newSiteId = newSite.getId().toString(); 230 authorizationRequest.getExtensions().put(APPROVED_SITE, newSiteId); 231 } 232 233 setAuthTime(authorizationRequest); 234 235 236 } 237 238 return authorizationRequest; 239 } 240 241 /** 242 * Get the auth time out of the current session and add it to the 243 * auth request in the extensions map. 244 * 245 * @param authorizationRequest 246 */ 247 private void setAuthTime(AuthorizationRequest authorizationRequest) { 248 // Get the session auth time, if we have it, and store it in the request 249 ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); 250 if (attr != null) { 251 HttpSession session = attr.getRequest().getSession(); 252 if (session != null) { 253 Date authTime = (Date) session.getAttribute(AuthenticationTimeStamper.AUTH_TIMESTAMP); 254 if (authTime != null) { 255 String authTimeString = Long.toString(authTime.getTime()); 256 authorizationRequest.getExtensions().put(AuthenticationTimeStamper.AUTH_TIMESTAMP, authTimeString); 257 } 258 } 259 } 260 } 261 262 @Override 263 public Map<String, Object> getUserApprovalRequest(AuthorizationRequest authorizationRequest, 264 Authentication userAuthentication) { 265 Map<String, Object> model = new HashMap<>(); 266 // In case of a redirect we might want the request parameters to be included 267 model.putAll(authorizationRequest.getRequestParameters()); 268 return model; 269 } 270 271}