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}