org.mitre.openid.connect.token.TofuUserApprovalHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.mitre.openid.connect.token.TofuUserApprovalHandler.java

Source

/*******************************************************************************
 * Copyright 2016 The MITRE Corporation
 *   and the MIT Internet Trust Consortium
 *
 * 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.model.SystemScope;
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)) {

                        // If it's structured, assign the user-specified parameter
                        SystemScope systemScope = systemScopes.getByValue(scope);
                        if (systemScope != null && systemScope.isStructured()) {
                            String paramValue = approvalParams.get("scopeparam_" + scope);
                            allowedScopes.add(scope + ":" + paramValue);
                            // .. and if it's unstructured, we're all set
                        } else {
                            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;
    }

}