001/* 002 * Copyright 2023 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.workspaces.extrausermgt.authentication.oauth; 017 018import java.net.URI; 019import java.util.Arrays; 020import java.util.HashMap; 021import java.util.Iterator; 022import java.util.List; 023import java.util.Map; 024 025import org.apache.avalon.framework.parameters.Parameters; 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.avalon.framework.service.Serviceable; 029import org.apache.avalon.framework.thread.ThreadSafe; 030import org.apache.cocoon.acting.AbstractAction; 031import org.apache.cocoon.environment.ObjectModelHelper; 032import org.apache.cocoon.environment.Redirector; 033import org.apache.cocoon.environment.Request; 034import org.apache.cocoon.environment.Session; 035import org.apache.cocoon.environment.SourceResolver; 036 037import org.ametys.plugins.extrausermgt.oauth.DefaultOauthProvider; 038import org.ametys.plugins.extrausermgt.oauth.OAuthProvider; 039import org.ametys.plugins.extrausermgt.oauth.OauthProviderExtensionPoint; 040import org.ametys.runtime.authentication.AccessDeniedException; 041 042import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant; 043import com.nimbusds.oauth2.sdk.AuthorizationResponse; 044import com.nimbusds.oauth2.sdk.ErrorObject; 045import com.nimbusds.oauth2.sdk.id.State; 046 047/** 048 * Action intended to manage result of an authorize request. 049 * 050 * This action will check the response, retrieve the authorization code 051 * use it to request an access token and finally store it in session before 052 * redirecting to the original request that redirected to the authorize request 053 */ 054public class OAuthCallbackAction extends AbstractAction implements ThreadSafe, Serviceable 055{ 056 private OauthProviderExtensionPoint _oauthEP; 057 058 public void service(ServiceManager manager) throws ServiceException 059 { 060 _oauthEP = (OauthProviderExtensionPoint) manager.lookup(OauthProviderExtensionPoint.ROLE); 061 } 062 063 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 064 { 065 Request request = ObjectModelHelper.getRequest(objectModel); 066 Map<String, List<String>> params = new HashMap<>(); 067 for (Iterator paramIterator = request.getParameterNames().asIterator(); paramIterator.hasNext();) 068 { 069 String paramName = (String) paramIterator.next(); 070 params.put(paramName, Arrays.asList(request.getParameterValues(paramName))); 071 } 072 073 AuthorizationResponse response = AuthorizationResponse.parse(URI.create(request.getRequestURI()), params); 074 075 Session session = request.getSession(); 076 if (!response.indicatesSuccess()) 077 { 078 ErrorObject errorObject = response.toErrorResponse().getErrorObject(); 079 throw new AccessDeniedException("Oauth authorization request failed with http status '" + errorObject.getHTTPStatusCode() 080 + "', code '" + errorObject.getCode() 081 + "' and description '" + errorObject.getDescription() + "'."); 082 } 083 084 // check that response match the state saved for this request 085 checkResponseIntegrity(response, session); 086 087 // use the state to retrieve the provider linked to this response 088 OAuthProvider provider = _oauthEP.getProviderForState(response.getState()); 089 090 // get the access token and store it 091 AuthorizationCodeGrant grant = new AuthorizationCodeGrant(response.toSuccessResponse().getAuthorizationCode(), URI.create(request.getRequestURI())); 092 provider.requestAccessToken(grant); 093 094 // we are done with the authorization process. 095 // we can now redirect to the original request requiring a protected resource 096 String originalRequest = (String) session.getAttribute(DefaultOauthProvider.OAUTH_REDIRECT_URI_SESSION_ATTRIBUTE); 097 if (!redirector.hasRedirected()) 098 { 099 redirector.redirect(false, originalRequest); 100 } 101 102 return EMPTY_MAP; 103 } 104 105 /** 106 * Check the response integrity 107 * @param response the authorize request response 108 * @param session the current session 109 */ 110 protected void checkResponseIntegrity(AuthorizationResponse response, Session session) 111 { 112 State state = (State) session.getAttribute(DefaultOauthProvider.OAUTH_STATE_SESSION_ATTRIBUTE); 113 if (state == null || !state.equals(response.getState())) 114 { 115 throw new AccessDeniedException("Failed to retrieve the authorization code. Oauth state mismatch."); 116 } 117 else 118 { 119 // the session state was successfully used to unsure that the response is linked to the current session. 120 // we can now remove it as it fulfilled its purpose 121 session.removeAttribute(DefaultOauthProvider.OAUTH_STATE_SESSION_ATTRIBUTE); 122 } 123 } 124}