TofuUserApprovalHandler.java
/*******************************************************************************
* Copyright 2017 The MIT Internet Trust Consortium
*
* Portions copyright 2011-2013 The MITRE Corporation
*
* 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.openid.connect.token;
import static org.mitre.openid.connect.request.ConnectRequestParameters.APPROVED_SITE;
import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT;
import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_CONSENT;
import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_SEPARATOR;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpSession;
import org.mitre.oauth2.service.SystemScopeService;
import org.mitre.openid.connect.model.ApprovedSite;
import org.mitre.openid.connect.model.WhitelistedSite;
import org.mitre.openid.connect.service.ApprovedSiteService;
import org.mitre.openid.connect.service.WhitelistedSiteService;
import org.mitre.openid.connect.web.AuthenticationTimeStamper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.approval.UserApprovalHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
/**
* Custom User Approval Handler implementation which uses a concept of a whitelist,
* blacklist, and greylist.
*
* Blacklisted sites will be caught and handled before this
* point.
*
* Whitelisted sites will be automatically approved, and an ApprovedSite entry will
* be created for the site the first time a given user access it.
*
* All other sites fall into the greylist - the user will be presented with the user
* approval page upon their first visit
* @author aanganes
*
*/
@Component("tofuUserApprovalHandler")
public class TofuUserApprovalHandler implements UserApprovalHandler {
@Autowired
private ApprovedSiteService approvedSiteService;
@Autowired
private WhitelistedSiteService whitelistedSiteService;
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private SystemScopeService systemScopes;
/**
* Check if the user has already stored a positive approval decision for this site; or if the
* site is whitelisted, approve it automatically.
*
* Otherwise, return false so that the user will see the approval page and can make their own decision.
*
* @param authorizationRequest the incoming authorization request
* @param userAuthentication the Principal representing the currently-logged-in user
*
* @return true if the site is approved, false otherwise
*/
@Override
public boolean isApproved(AuthorizationRequest authorizationRequest, Authentication userAuthentication) {
// if this request is already approved, pass that info through
// (this flag may be set by updateBeforeApproval, which can also do funny things with scopes, etc)
if (authorizationRequest.isApproved()) {
return true;
} else {
// if not, check to see if the user has approved it
// TODO: make parameter name configurable?
return Boolean.parseBoolean(authorizationRequest.getApprovalParameters().get("user_oauth_approval"));
}
}
/**
* Check if the user has already stored a positive approval decision for this site; or if the
* site is whitelisted, approve it automatically.
*
* Otherwise the user will be directed to the approval page and can make their own decision.
*
* @param authorizationRequest the incoming authorization request
* @param userAuthentication the Principal representing the currently-logged-in user
*
* @return the updated AuthorizationRequest
*/
@Override
public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest, Authentication userAuthentication) {
//First, check database to see if the user identified by the userAuthentication has stored an approval decision
String userId = userAuthentication.getName();
String clientId = authorizationRequest.getClientId();
//lookup ApprovedSites by userId and clientId
boolean alreadyApproved = false;
// find out if we're supposed to force a prompt on the user or not
String prompt = (String) authorizationRequest.getExtensions().get(PROMPT);
List<String> prompts = Splitter.on(PROMPT_SEPARATOR).splitToList(Strings.nullToEmpty(prompt));
if (!prompts.contains(PROMPT_CONSENT)) {
// if the prompt parameter is set to "consent" then we can't use approved sites or whitelisted sites
// otherwise, we need to check them below
Collection<ApprovedSite> aps = approvedSiteService.getByClientIdAndUserId(clientId, userId);
for (ApprovedSite ap : aps) {
if (!ap.isExpired()) {
// if we find one that fits...
if (systemScopes.scopesMatch(ap.getAllowedScopes(), authorizationRequest.getScope())) {
//We have a match; update the access date on the AP entry and return true.
ap.setAccessDate(new Date());
approvedSiteService.save(ap);
String apId = ap.getId().toString();
authorizationRequest.getExtensions().put(APPROVED_SITE, apId);
authorizationRequest.setApproved(true);
alreadyApproved = true;
setAuthTime(authorizationRequest);
}
}
}
if (!alreadyApproved) {
WhitelistedSite ws = whitelistedSiteService.getByClientId(clientId);
if (ws != null && systemScopes.scopesMatch(ws.getAllowedScopes(), authorizationRequest.getScope())) {
authorizationRequest.setApproved(true);
setAuthTime(authorizationRequest);
}
}
}
return authorizationRequest;
}
@Override
public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest, Authentication userAuthentication) {
String userId = userAuthentication.getName();
String clientId = authorizationRequest.getClientId();
ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
// This must be re-parsed here because SECOAUTH forces us to call things in a strange order
if (Boolean.parseBoolean(authorizationRequest.getApprovalParameters().get("user_oauth_approval"))) {
authorizationRequest.setApproved(true);
// process scopes from user input
Set<String> allowedScopes = Sets.newHashSet();
Map<String,String> approvalParams = authorizationRequest.getApprovalParameters();
Set<String> keys = approvalParams.keySet();
for (String key : keys) {
if (key.startsWith("scope_")) {
//This is a scope parameter from the approval page. The value sent back should
//be the scope string. Check to make sure it is contained in the client's
//registered allowed scopes.
String scope = approvalParams.get(key);
Set<String> approveSet = Sets.newHashSet(scope);
//Make sure this scope is allowed for the given client
if (systemScopes.scopesMatch(client.getScope(), approveSet)) {
allowedScopes.add(scope);
}
}
}
// inject the user-allowed scopes into the auth request
authorizationRequest.setScope(allowedScopes);
//Only store an ApprovedSite if the user has checked "remember this decision":
String remember = authorizationRequest.getApprovalParameters().get("remember");
if (!Strings.isNullOrEmpty(remember) && !remember.equals("none")) {
Date timeout = null;
if (remember.equals("one-hour")) {
// set the timeout to one hour from now
Calendar cal = Calendar.getInstance();
cal.add(Calendar.HOUR, 1);
timeout = cal.getTime();
}
ApprovedSite newSite = approvedSiteService.createApprovedSite(clientId, userId, timeout, allowedScopes);
String newSiteId = newSite.getId().toString();
authorizationRequest.getExtensions().put(APPROVED_SITE, newSiteId);
}
setAuthTime(authorizationRequest);
}
return authorizationRequest;
}
/**
* Get the auth time out of the current session and add it to the
* auth request in the extensions map.
*
* @param authorizationRequest
*/
private void setAuthTime(AuthorizationRequest authorizationRequest) {
// Get the session auth time, if we have it, and store it in the request
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
if (attr != null) {
HttpSession session = attr.getRequest().getSession();
if (session != null) {
Date authTime = (Date) session.getAttribute(AuthenticationTimeStamper.AUTH_TIMESTAMP);
if (authTime != null) {
String authTimeString = Long.toString(authTime.getTime());
authorizationRequest.getExtensions().put(AuthenticationTimeStamper.AUTH_TIMESTAMP, authTimeString);
}
}
}
}
@Override
public Map<String, Object> getUserApprovalRequest(AuthorizationRequest authorizationRequest,
Authentication userAuthentication) {
Map<String, Object> model = new HashMap<>();
// In case of a redirect we might want the request parameters to be included
model.putAll(authorizationRequest.getRequestParameters());
return model;
}
}