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}