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.oauth2.web;
019
020import static org.mitre.oauth2.web.AuthenticationUtilities.ensureOAuthScope;
021
022import java.util.Collection;
023import java.util.HashSet;
024import java.util.Map;
025import java.util.Set;
026
027import org.mitre.oauth2.model.ClientDetailsEntity;
028import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
029import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
030import org.mitre.oauth2.service.ClientDetailsEntityService;
031import org.mitre.oauth2.service.IntrospectionResultAssembler;
032import org.mitre.oauth2.service.OAuth2TokenEntityService;
033import org.mitre.oauth2.service.SystemScopeService;
034import org.mitre.openid.connect.model.UserInfo;
035import org.mitre.openid.connect.service.UserInfoService;
036import org.mitre.openid.connect.view.HttpCodeView;
037import org.mitre.openid.connect.view.JsonEntityView;
038import org.mitre.uma.model.ResourceSet;
039import org.mitre.uma.service.ResourceSetService;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042import org.springframework.beans.factory.annotation.Autowired;
043import org.springframework.http.HttpStatus;
044import org.springframework.security.core.Authentication;
045import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
046import org.springframework.security.oauth2.provider.OAuth2Authentication;
047import org.springframework.stereotype.Controller;
048import org.springframework.ui.Model;
049import org.springframework.web.bind.annotation.RequestMapping;
050import org.springframework.web.bind.annotation.RequestParam;
051
052import com.google.common.base.Strings;
053import com.google.common.collect.ImmutableMap;
054
055@Controller
056public class IntrospectionEndpoint {
057
058        /**
059         *
060         */
061        public static final String URL = "introspect";
062
063        @Autowired
064        private OAuth2TokenEntityService tokenServices;
065
066        @Autowired
067        private ClientDetailsEntityService clientService;
068
069        @Autowired
070        private IntrospectionResultAssembler introspectionResultAssembler;
071
072        @Autowired
073        private UserInfoService userInfoService;
074
075        @Autowired
076        private ResourceSetService resourceSetService;
077
078        /**
079         * Logger for this class
080         */
081        private static final Logger logger = LoggerFactory.getLogger(IntrospectionEndpoint.class);
082
083        public IntrospectionEndpoint() {
084
085        }
086
087        public IntrospectionEndpoint(OAuth2TokenEntityService tokenServices) {
088                this.tokenServices = tokenServices;
089        }
090
091        @RequestMapping("/" + URL)
092        public String verify(@RequestParam("token") String tokenValue,
093                        @RequestParam(value = "token_type_hint", required = false) String tokenType,
094                        Authentication auth, Model model) {
095
096                ClientDetailsEntity authClient = null;
097                Set<String> authScopes = new HashSet<>();
098
099                if (auth instanceof OAuth2Authentication) {
100                        // the client authenticated with OAuth, do our UMA checks
101                        ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE);
102
103                        // get out the client that was issued the access token (not the token being introspected)
104                        OAuth2Authentication o2a = (OAuth2Authentication) auth;
105
106                        String authClientId = o2a.getOAuth2Request().getClientId();
107                        authClient = clientService.loadClientByClientId(authClientId);
108
109                        // the owner is the user who authorized the token in the first place
110                        String ownerId = o2a.getUserAuthentication().getName();
111
112                        authScopes.addAll(authClient.getScope());
113
114                        // UMA style clients also get a subset of scopes of all the resource sets they've registered
115                        Collection<ResourceSet> resourceSets = resourceSetService.getAllForOwnerAndClient(ownerId, authClientId);
116
117                        // collect all the scopes
118                        for (ResourceSet rs : resourceSets) {
119                                authScopes.addAll(rs.getScopes());
120                        }
121
122                } else {
123                        // the client authenticated directly, make sure it's got the right access
124
125                        String authClientId = auth.getName(); // direct authentication puts the client_id into the authentication's name field
126                        authClient = clientService.loadClientByClientId(authClientId);
127
128                        // directly authenticated clients get a subset of any scopes that they've registered for
129                        authScopes.addAll(authClient.getScope());
130
131                        if (!AuthenticationUtilities.hasRole(auth, "ROLE_CLIENT")
132                                        || !authClient.isAllowIntrospection()) {
133
134                                // this client isn't allowed to do direct introspection
135
136                                logger.error("Client " + authClient.getClientId() + " is not allowed to call introspection endpoint");
137                                model.addAttribute("code", HttpStatus.FORBIDDEN);
138                                return HttpCodeView.VIEWNAME;
139
140                        }
141
142                }
143
144                // by here we're allowed to introspect, now we need to look up the token in our token stores
145
146                // first make sure the token is there
147                if (Strings.isNullOrEmpty(tokenValue)) {
148                        logger.error("Verify failed; token value is null");
149                        Map<String,Boolean> entity = ImmutableMap.of("active", Boolean.FALSE);
150                        model.addAttribute(JsonEntityView.ENTITY, entity);
151                        return JsonEntityView.VIEWNAME;
152                }
153
154                OAuth2AccessTokenEntity accessToken = null;
155                OAuth2RefreshTokenEntity refreshToken = null;
156                ClientDetailsEntity tokenClient;
157                UserInfo user;
158
159                try {
160
161                        // check access tokens first (includes ID tokens)
162                        accessToken = tokenServices.readAccessToken(tokenValue);
163
164                        tokenClient = accessToken.getClient();
165
166                        // get the user information of the user that authorized this token in the first place
167                        String userName = accessToken.getAuthenticationHolder().getAuthentication().getName();
168                        user = userInfoService.getByUsernameAndClientId(userName, tokenClient.getClientId());
169
170                } catch (InvalidTokenException e) {
171                        logger.info("Invalid access token. Checking refresh token.");
172                        try {
173
174                                // check refresh tokens next
175                                refreshToken = tokenServices.getRefreshToken(tokenValue);
176
177                                tokenClient = refreshToken.getClient();
178
179                                // get the user information of the user that authorized this token in the first place
180                                String userName = refreshToken.getAuthenticationHolder().getAuthentication().getName();
181                                user = userInfoService.getByUsernameAndClientId(userName, tokenClient.getClientId());
182
183                        } catch (InvalidTokenException e2) {
184                                logger.error("Invalid refresh token");
185                                Map<String,Boolean> entity = ImmutableMap.of(IntrospectionResultAssembler.ACTIVE, Boolean.FALSE);
186                                model.addAttribute(JsonEntityView.ENTITY, entity);
187                                return JsonEntityView.VIEWNAME;
188                        }
189                }
190
191                // if it's a valid token, we'll print out information on it
192
193                if (accessToken != null) {
194                        Map<String, Object> entity = introspectionResultAssembler.assembleFrom(accessToken, user, authScopes);
195                        model.addAttribute(JsonEntityView.ENTITY, entity);
196                } else if (refreshToken != null) {
197                        Map<String, Object> entity = introspectionResultAssembler.assembleFrom(refreshToken, user, authScopes);
198                        model.addAttribute(JsonEntityView.ENTITY, entity);
199                } else {
200                        // no tokens were found (we shouldn't get here)
201                        logger.error("Verify failed; Invalid access/refresh token");
202                        Map<String,Boolean> entity = ImmutableMap.of(IntrospectionResultAssembler.ACTIVE, Boolean.FALSE);
203                        model.addAttribute(JsonEntityView.ENTITY, entity);
204                        return JsonEntityView.VIEWNAME;
205                }
206
207                return JsonEntityView.VIEWNAME;
208
209        }
210
211}