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}