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 org.mitre.oauth2.model.ClientDetailsEntity; 023import org.mitre.oauth2.model.OAuth2AccessTokenEntity; 024import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; 025import org.mitre.oauth2.service.ClientDetailsEntityService; 026import org.mitre.oauth2.service.OAuth2TokenEntityService; 027import org.mitre.oauth2.service.SystemScopeService; 028import org.mitre.openid.connect.view.HttpCodeView; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031import org.springframework.beans.factory.annotation.Autowired; 032import org.springframework.http.HttpStatus; 033import org.springframework.security.access.prepost.PreAuthorize; 034import org.springframework.security.core.Authentication; 035import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; 036import org.springframework.security.oauth2.provider.OAuth2Authentication; 037import org.springframework.stereotype.Controller; 038import org.springframework.ui.Model; 039import org.springframework.web.bind.annotation.RequestMapping; 040import org.springframework.web.bind.annotation.RequestParam; 041 042@Controller 043public class RevocationEndpoint { 044 @Autowired 045 private ClientDetailsEntityService clientService; 046 047 @Autowired 048 private OAuth2TokenEntityService tokenServices; 049 050 /** 051 * Logger for this class 052 */ 053 private static final Logger logger = LoggerFactory.getLogger(RevocationEndpoint.class); 054 055 public static final String URL = "revoke"; 056 057 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')") 058 @RequestMapping("/" + URL) 059 public String revoke(@RequestParam("token") String tokenValue, @RequestParam(value = "token_type_hint", required = false) String tokenType, Authentication auth, Model model) { 060 061 // This is the token as passed in from OAuth (in case we need it some day) 062 //OAuth2AccessTokenEntity tok = tokenServices.getAccessToken((OAuth2Authentication) principal); 063 064 ClientDetailsEntity authClient = null; 065 066 if (auth instanceof OAuth2Authentication) { 067 // the client authenticated with OAuth, do our UMA checks 068 ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE); 069 // get out the client that was issued the access token (not the token being revoked) 070 OAuth2Authentication o2a = (OAuth2Authentication) auth; 071 072 String authClientId = o2a.getOAuth2Request().getClientId(); 073 authClient = clientService.loadClientByClientId(authClientId); 074 075 // the owner is the user who authorized the token in the first place 076 String ownerId = o2a.getUserAuthentication().getName(); 077 078 } else { 079 // the client authenticated directly, make sure it's got the right access 080 081 String authClientId = auth.getName(); // direct authentication puts the client_id into the authentication's name field 082 authClient = clientService.loadClientByClientId(authClientId); 083 084 } 085 086 try { 087 // check and handle access tokens first 088 089 OAuth2AccessTokenEntity accessToken = tokenServices.readAccessToken(tokenValue); 090 091 // client acting on its own, make sure it owns the token 092 if (!accessToken.getClient().getClientId().equals(authClient.getClientId())) { 093 // trying to revoke a token we don't own, throw a 403 094 095 logger.info("Client " + authClient.getClientId() + " tried to revoke a token owned by " + accessToken.getClient().getClientId()); 096 097 model.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); 098 return HttpCodeView.VIEWNAME; 099 } 100 101 // if we got this far, we're allowed to do this 102 tokenServices.revokeAccessToken(accessToken); 103 104 logger.debug("Client " + authClient.getClientId() + " revoked access token " + tokenValue); 105 106 model.addAttribute(HttpCodeView.CODE, HttpStatus.OK); 107 return HttpCodeView.VIEWNAME; 108 109 } catch (InvalidTokenException e) { 110 111 // access token wasn't found, check the refresh token 112 113 try { 114 OAuth2RefreshTokenEntity refreshToken = tokenServices.getRefreshToken(tokenValue); 115 // client acting on its own, make sure it owns the token 116 if (!refreshToken.getClient().getClientId().equals(authClient.getClientId())) { 117 // trying to revoke a token we don't own, throw a 403 118 119 logger.info("Client " + authClient.getClientId() + " tried to revoke a token owned by " + refreshToken.getClient().getClientId()); 120 121 model.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); 122 return HttpCodeView.VIEWNAME; 123 } 124 125 // if we got this far, we're allowed to do this 126 tokenServices.revokeRefreshToken(refreshToken); 127 128 logger.debug("Client " + authClient.getClientId() + " revoked access token " + tokenValue); 129 130 model.addAttribute(HttpCodeView.CODE, HttpStatus.OK); 131 return HttpCodeView.VIEWNAME; 132 133 } catch (InvalidTokenException e1) { 134 135 // neither token type was found, simply say "OK" and be on our way. 136 137 logger.debug("Failed to revoke token " + tokenValue); 138 139 model.addAttribute(HttpCodeView.CODE, HttpStatus.OK); 140 return HttpCodeView.VIEWNAME; 141 } 142 } 143 } 144 145}