verifyAccessForGrantAuthorizationCode
last analyzed

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
c 0
b 0
f 0
1
package org.apereo.cas.support.oauth.web.endpoints;
2
3
import com.google.common.base.Supplier;
4
import org.apache.commons.lang3.tuple.Pair;
5
import org.apereo.cas.authentication.principal.PrincipalFactory;
6
import org.apereo.cas.authentication.principal.ServiceFactory;
7
import org.apereo.cas.authentication.principal.WebApplicationService;
8
import org.apereo.cas.configuration.CasConfigurationProperties;
9
import org.apereo.cas.services.ServicesManager;
10
import org.apereo.cas.support.oauth.OAuth20Constants;
11
import org.apereo.cas.support.oauth.OAuth20GrantTypes;
12
import org.apereo.cas.support.oauth.OAuth20ResponseTypes;
13
import org.apereo.cas.support.oauth.profile.OAuth20ProfileScopeToAttributesFilter;
14
import org.apereo.cas.support.oauth.services.OAuthRegisteredService;
15
import org.apereo.cas.support.oauth.util.OAuth20Utils;
16
import org.apereo.cas.support.oauth.validator.OAuth20Validator;
17
import org.apereo.cas.support.oauth.web.response.accesstoken.AccessTokenResponseGenerator;
18
import org.apereo.cas.support.oauth.web.response.accesstoken.OAuth20TokenGenerator;
19
import org.apereo.cas.support.oauth.web.response.accesstoken.ext.AccessTokenRequestDataHolder;
20
import org.apereo.cas.support.oauth.web.response.accesstoken.ext.BaseAccessTokenGrantRequestExtractor;
21
import org.apereo.cas.ticket.ExpirationPolicy;
22
import org.apereo.cas.ticket.Ticket;
23
import org.apereo.cas.ticket.accesstoken.AccessToken;
24
import org.apereo.cas.ticket.accesstoken.AccessTokenFactory;
25
import org.apereo.cas.ticket.refreshtoken.RefreshToken;
26
import org.apereo.cas.ticket.registry.TicketRegistry;
27
import org.apereo.cas.util.Pac4jUtils;
28
import org.apereo.cas.web.support.CookieRetrievingCookieGenerator;
29
import org.pac4j.core.context.J2EContext;
30
import org.pac4j.core.profile.ProfileManager;
31
import org.pac4j.core.profile.UserProfile;
32
import org.slf4j.Logger;
33
import org.slf4j.LoggerFactory;
34
import org.springframework.http.MediaType;
35
import org.springframework.web.bind.annotation.GetMapping;
36
import org.springframework.web.bind.annotation.PostMapping;
37
38
import javax.servlet.http.HttpServletRequest;
39
import javax.servlet.http.HttpServletResponse;
40
import java.util.Collection;
41
import java.util.Optional;
42
43
/**
44
 * This controller returns an access token according to the given
45
 * OAuth code and client credentials (authorization code grant type)
46
 * or according to the refresh token and client credentials
47
 * (refresh token grant type) or according to the user identity
48
 * (resource owner password grant type).
49
 *
50
 * @author Jerome Leleu
51
 * @since 3.5.0
52
 */
53
public class OAuth20AccessTokenEndpointController extends BaseOAuth20Controller {
54
    private static final Logger LOGGER = LoggerFactory.getLogger(OAuth20AccessTokenEndpointController.class);
55
56
    private final OAuth20TokenGenerator accessTokenGenerator;
57
    private final AccessTokenResponseGenerator accessTokenResponseGenerator;
58
59
    private final ExpirationPolicy accessTokenExpirationPolicy;
60
    private final Collection<BaseAccessTokenGrantRequestExtractor> accessTokenGrantRequestExtractors;
61
62
    public OAuth20AccessTokenEndpointController(final ServicesManager servicesManager,
63
                                                final TicketRegistry ticketRegistry,
64
                                                final OAuth20Validator validator,
65
                                                final AccessTokenFactory accessTokenFactory,
66
                                                final PrincipalFactory principalFactory,
67
                                                final ServiceFactory<WebApplicationService> webApplicationServiceServiceFactory,
68
                                                final OAuth20TokenGenerator accessTokenGenerator,
69
                                                final AccessTokenResponseGenerator accessTokenResponseGenerator,
70
                                                final OAuth20ProfileScopeToAttributesFilter scopeToAttributesFilter,
71
                                                final CasConfigurationProperties casProperties,
72
                                                final CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator,
73
                                                final ExpirationPolicy accessTokenExpirationPolicy,
74
                                                final Collection<BaseAccessTokenGrantRequestExtractor> accessTokenGrantRequestExtractors) {
75
        super(servicesManager, ticketRegistry, validator, accessTokenFactory,
76
                principalFactory, webApplicationServiceServiceFactory,
77
                scopeToAttributesFilter, casProperties, ticketGrantingTicketCookieGenerator);
78
        this.accessTokenGenerator = accessTokenGenerator;
79
        this.accessTokenResponseGenerator = accessTokenResponseGenerator;
80
        this.accessTokenExpirationPolicy = accessTokenExpirationPolicy;
81
        this.accessTokenGrantRequestExtractors = accessTokenGrantRequestExtractors;
82
    }
83
84
    /**
85
     * Handle request internal model and view.
86
     *
87
     * @param request  the request
88
     * @param response the response
89
     * @throws Exception the exception
90
     */
91
    @PostMapping(path = {OAuth20Constants.BASE_OAUTH20_URL + '/' + OAuth20Constants.ACCESS_TOKEN_URL,
92
            OAuth20Constants.BASE_OAUTH20_URL + '/' + OAuth20Constants.TOKEN_URL})
93
    public void handleRequest(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
94
        try {
95
            response.setContentType(MediaType.TEXT_PLAIN_VALUE);
96
97
            if (!verifyAccessTokenRequest(request, response)) {
98
                LOGGER.error("Access token request verification failed");
99
                OAuth20Utils.writeTextError(response, OAuth20Constants.INVALID_REQUEST);
100
                return;
101
            }
102
103
            final AccessTokenRequestDataHolder responseHolder;
104
            try {
105
                responseHolder = examineAndExtractAccessTokenGrantRequest(request, response);
106
                LOGGER.debug("Creating access token for [{}]", responseHolder);
107
            } catch (final Exception e) {
108
                LOGGER.error("Could not identify and extract access token request", e);
109
                OAuth20Utils.writeTextError(response, OAuth20Constants.INVALID_GRANT);
110
                return;
111
            }
112
113
            final J2EContext context = Pac4jUtils.getPac4jJ2EContext(request, response);
114
            final Pair<AccessToken, RefreshToken> accessToken = accessTokenGenerator.generate(responseHolder);
115
            LOGGER.debug("Access token generated is: [{}]. Refresh token generated is [{}]", accessToken.getKey(), accessToken.getValue());
116
            generateAccessTokenResponse(request, response, responseHolder, context, accessToken.getKey(), accessToken.getValue());
117
            response.setStatus(HttpServletResponse.SC_OK);
118
        } catch (final Exception e) {
119
            LOGGER.error(e.getMessage(), e);
120
            throw new RuntimeException(e.getMessage(), e);
121
        }
122
    }
123
124
    /**
125
     * Handle request internal model and view.
126
     *
127
     * @param request  the request
128
     * @param response the response
129
     * @throws Exception the exception
130
     */
131
    @GetMapping(path = {OAuth20Constants.BASE_OAUTH20_URL + '/' + OAuth20Constants.ACCESS_TOKEN_URL,
132
            OAuth20Constants.BASE_OAUTH20_URL + '/' + OAuth20Constants.TOKEN_URL})
133
    public void handleGetRequest(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
134
        handleRequest(request, response);
135
    }
136
137
138
    private void generateAccessTokenResponse(final HttpServletRequest request, final HttpServletResponse response,
139
                                             final AccessTokenRequestDataHolder responseHolder,
140
                                             final J2EContext context, final AccessToken accessToken,
141
                                             final RefreshToken refreshToken) {
142
        LOGGER.debug("Generating access token response for [{}]", accessToken);
143
144
        final OAuth20ResponseTypes type = OAuth20Utils.getResponseType(context);
145
        LOGGER.debug("Located response type as [{}]", type);
146
147
        this.accessTokenResponseGenerator.generate(request, response,
148
                responseHolder.getRegisteredService(),
149
                responseHolder.getService(),
150
                accessToken, refreshToken,
151
                accessTokenExpirationPolicy.getTimeToLive(), type);
152
    }
153
154
    private AccessTokenRequestDataHolder examineAndExtractAccessTokenGrantRequest(final HttpServletRequest request,
155
                                                                                  final HttpServletResponse response) {
156
        return this.accessTokenGrantRequestExtractors.stream()
157
                .filter(ext -> ext.supports(request))
158
                .findFirst()
159
                .orElseThrow((Supplier<RuntimeException>) () -> new UnsupportedOperationException("Request is not supported"))
160
                .extract(request, response);
161
    }
162
163
    /**
164
     * Verify the access token request.
165
     *
166
     * @param request  the HTTP request
167
     * @param response the HTTP response
168
     * @return true, if successful
169
     */
170
    private boolean verifyAccessTokenRequest(final HttpServletRequest request, final HttpServletResponse response) {
171
        final String grantType = request.getParameter(OAuth20Constants.GRANT_TYPE);
172
        if (!isGrantTypeSupported(grantType, OAuth20GrantTypes.values())) {
173
            LOGGER.warn("Grant type is not supported: [{}]", grantType);
174
            return false;
175
        }
176
177
        final ProfileManager manager = Pac4jUtils.getPac4jProfileManager(request, response);
178
        final Optional<UserProfile> profile = manager.get(true);
179
        if (profile == null || !profile.isPresent()) {
180
            LOGGER.warn("Could not locate authenticated profile for this request");
181
            return false;
182
        }
183
184
        final UserProfile uProfile = profile.get();
185
        if (uProfile == null) {
186
            LOGGER.warn("Could not locate authenticated profile for this request as null");
187
            return false;
188
        }
189
        if (OAuth20Utils.isGrantType(grantType, OAuth20GrantTypes.AUTHORIZATION_CODE)) {
190
            return verifyAccessForGrantAuthorizationCode(request, grantType, uProfile);
191
        }
192
193
        if (OAuth20Utils.isGrantType(grantType, OAuth20GrantTypes.REFRESH_TOKEN)) {
194
            return verifyAccessForGrantRefreshToken(request, uProfile);
195
        }
196
197
        if (OAuth20Utils.isGrantType(grantType, OAuth20GrantTypes.PASSWORD)) {
198
            return verifyAccessForGrantPassword(request, grantType, uProfile);
199
        }
200
201
        if (OAuth20Utils.isGrantType(grantType, OAuth20GrantTypes.CLIENT_CREDENTIALS)) {
202
            return verifyAccessForGrantClientCredentials(request, grantType, uProfile);
203
        }
204
205
        return false;
206
    }
207
208
    private boolean verifyAccessForGrantClientCredentials(final HttpServletRequest request, final String grantType, final UserProfile uProfile) {
209
        final String clientId = request.getParameter(OAuth20Constants.CLIENT_ID);
210
        LOGGER.debug("Received grant type [{}] with client id [{}]", grantType, clientId);
211
        final OAuthRegisteredService registeredService = OAuth20Utils.getRegisteredOAuthService(this.servicesManager, clientId);
212
        return this.validator.checkParameterExist(request, OAuth20Constants.CLIENT_ID)
213
                && this.validator.checkServiceValid(registeredService);
214
    }
215
216
    private boolean verifyAccessForGrantPassword(final HttpServletRequest request, final String grantType, final UserProfile uProfile) {
217
        final String clientId = request.getParameter(OAuth20Constants.CLIENT_ID);
218
        LOGGER.debug("Received grant type [{}] with client id [{}]", grantType, clientId);
219
        final OAuthRegisteredService registeredService = OAuth20Utils.getRegisteredOAuthService(this.servicesManager, clientId);
220
221
        return this.validator.checkParameterExist(request, OAuth20Constants.CLIENT_ID)
222
                && this.validator.checkServiceValid(registeredService);
223
    }
224
225
    private boolean verifyAccessForGrantRefreshToken(final HttpServletRequest request, final UserProfile uProfile) {
226
        if (!this.validator.checkParameterExist(request, OAuth20Constants.REFRESH_TOKEN)
227
                || !this.validator.checkParameterExist(request, OAuth20Constants.CLIENT_ID)
228
                || !this.validator.checkParameterExist(request, OAuth20Constants.CLIENT_SECRET)) {
229
            return false;
230
        }
231
        final String token = request.getParameter(OAuth20Constants.REFRESH_TOKEN);
232
        final Ticket refreshToken = ticketRegistry.getTicket(token);
233
        if (refreshToken == null) {
234
            LOGGER.warn("Provided refresh token [{}] cannot be found in the registry", token);
235
            return false;
236
        }
237
        if (!RefreshToken.class.isAssignableFrom(refreshToken.getClass())) {
238
            LOGGER.warn("Provided refresh token [{}] is found in the registry but its type is not classified as a refresh token", token);
239
            return false;
240
        }
241
        if (refreshToken.isExpired()) {
242
            LOGGER.warn("Provided refresh token [{}] has expired and is no longer valid.", token);
243
            return false;
244
        }
245
        return true;
246
    }
247
248
    private boolean verifyAccessForGrantAuthorizationCode(final HttpServletRequest request, final String grantType, final UserProfile uProfile) {
249
        final String clientId = uProfile.getId();
250
        final String redirectUri = request.getParameter(OAuth20Constants.REDIRECT_URI);
251
        final OAuthRegisteredService registeredService = OAuth20Utils.getRegisteredOAuthService(this.servicesManager, clientId);
252
253
        LOGGER.debug("Received grant type [{}] with client id [{}] and redirect URI [{}]", grantType, clientId, redirectUri);
254
255
        return this.validator.checkParameterExist(request, OAuth20Constants.REDIRECT_URI)
256
                && this.validator.checkParameterExist(request, OAuth20Constants.CODE)
257
                && this.validator.checkCallbackValid(registeredService, redirectUri);
258
    }
259
260
    /**
261
     * Check the grant type against expected grant types.
262
     *
263
     * @param type          the current grant type
264
     * @param expectedTypes the expected grant types
265
     * @return whether the grant type is supported
266
     */
267
    private static boolean isGrantTypeSupported(final String type, final OAuth20GrantTypes... expectedTypes) {
268
        LOGGER.debug("Grant type: [{}]", type);
269
270
        for (final OAuth20GrantTypes expectedType : expectedTypes) {
271
            if (OAuth20Utils.isGrantType(type, expectedType)) {
272
                return true;
273
            }
274
        }
275
        LOGGER.error("Unsupported grant type: [{}]", type);
276
        return false;
277
    }
278
}
279