Skip to content

Commit 7d6a93c

Browse files
Add api layer for api based authentication
1 parent a4c2702 commit 7d6a93c

File tree

22 files changed

+1695
-29
lines changed

22 files changed

+1695
-29
lines changed

components/org.wso2.carbon.identity.oauth.common/src/main/java/org/wso2/carbon/identity/oauth/common/OAuthConstants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ public final class OAuthConstants {
110110
public static final String AUTHENTICATED_IDPS = "AuthenticatedIdPs";
111111
public static final String SESSION_STATE = "session_state";
112112
public static final String STATE = "state";
113+
public static final String AUTHENTICATOR_IDP_SPLITTER = ":";
113114

114115
public static final String SECTOR_IDENTIFIER_URI = "sector_identifier_uri";
115116
public static final String SUBJECT_TYPE = "subject_type";
@@ -731,6 +732,7 @@ public static class ResponseModes {
731732
public static final String QUERY_JWT = "query.jwt";
732733
public static final String FRAGMENT_JWT = "fragment.jwt";
733734
public static final String FORM_POST_JWT = "form_post.jwt";
735+
public static final String DIRECT = "direct"; // Used for API based authentication.
734736
}
735737

736738
}

components/org.wso2.carbon.identity.oauth.endpoint/src/main/java/org/wso2/carbon/identity/oauth/endpoint/OAuthRequestWrapper.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import java.util.Collections;
2424
import java.util.Enumeration;
25+
import java.util.HashMap;
2526
import java.util.HashSet;
2627
import java.util.List;
2728
import java.util.Map;
@@ -39,6 +40,8 @@ public class OAuthRequestWrapper extends HttpServletRequestWrapper {
3940
private Map<String, List<String>> form;
4041
private Enumeration<String> parameterNames;
4142

43+
private boolean isInternalRequest = false;
44+
4245
@Deprecated
4346
public OAuthRequestWrapper(HttpServletRequest request, MultivaluedMap<String, String> form) {
4447

@@ -66,7 +69,7 @@ public OAuthRequestWrapper(HttpServletRequest request, Map<String, List<String>>
6669
public String getParameter(String name) {
6770

6871
String value = super.getParameter(name);
69-
if (value == null) {
72+
if (value == null || isInternalRequest) {
7073
if (CollectionUtils.isNotEmpty(form.get(name))) {
7174
value = form.get(name).get(0);
7275
}
@@ -79,4 +82,29 @@ public Enumeration<String> getParameterNames() {
7982

8083
return parameterNames;
8184
}
85+
86+
/**
87+
* Set whether the request is internal or not.
88+
* If the request is internal, the request parameters
89+
* in the wrapper will get priority over the servlet request.
90+
*
91+
* @param internalRequest Whether the request is internal or not.
92+
*/
93+
public void setInternalRequest(boolean internalRequest) {
94+
95+
isInternalRequest = internalRequest;
96+
}
97+
98+
@Override
99+
public Map<String, String[]> getParameterMap() {
100+
101+
Map<String, String[]> parameterMap = new HashMap<>(super.getParameterMap());
102+
103+
// Add form data to parameterMap.
104+
for (Map.Entry<String, List<String>> entry : form.entrySet()) {
105+
parameterMap.put(entry.getKey(), entry.getValue().toArray(new String[0]));
106+
}
107+
108+
return parameterMap;
109+
}
82110
}
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
/*
2+
* Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com) All Rights Reserved.
3+
*
4+
* WSO2 LLC. licenses this file to you under the Apache License,
5+
* Version 2.0 (the "License"); you may not use this file except
6+
* in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
19+
package org.wso2.carbon.identity.oauth.endpoint.api.auth;
20+
21+
import com.fasterxml.jackson.annotation.JsonInclude;
22+
import com.fasterxml.jackson.core.JsonProcessingException;
23+
import com.fasterxml.jackson.databind.ObjectMapper;
24+
import com.google.gson.Gson;
25+
import org.apache.commons.logging.Log;
26+
import org.apache.commons.logging.LogFactory;
27+
import org.wso2.carbon.identity.application.authentication.framework.AuthenticationService;
28+
import org.wso2.carbon.identity.application.authentication.framework.exception.auth.service.AuthServiceClientException;
29+
import org.wso2.carbon.identity.application.authentication.framework.exception.auth.service.AuthServiceException;
30+
import org.wso2.carbon.identity.application.authentication.framework.model.auth.service.AuthServiceErrorInfo;
31+
import org.wso2.carbon.identity.application.authentication.framework.model.auth.service.AuthServiceRequest;
32+
import org.wso2.carbon.identity.application.authentication.framework.model.auth.service.AuthServiceResponse;
33+
import org.wso2.carbon.identity.oauth.common.OAuth2ErrorCodes;
34+
import org.wso2.carbon.identity.oauth.common.OAuthConstants;
35+
import org.wso2.carbon.identity.oauth.endpoint.OAuthRequestWrapper;
36+
import org.wso2.carbon.identity.oauth.endpoint.api.auth.model.AuthRequest;
37+
import org.wso2.carbon.identity.oauth.endpoint.api.auth.model.AuthResponse;
38+
import org.wso2.carbon.identity.oauth.endpoint.authz.OAuth2AuthzEndpoint;
39+
import org.wso2.carbon.identity.oauth.endpoint.exception.InvalidRequestParentException;
40+
41+
import java.net.URISyntaxException;
42+
import java.nio.charset.StandardCharsets;
43+
import java.util.Base64;
44+
import java.util.Collections;
45+
import java.util.HashMap;
46+
import java.util.List;
47+
import java.util.Map;
48+
import java.util.stream.Collectors;
49+
50+
import javax.servlet.http.HttpServletRequest;
51+
import javax.servlet.http.HttpServletResponse;
52+
import javax.ws.rs.Consumes;
53+
import javax.ws.rs.POST;
54+
import javax.ws.rs.Path;
55+
import javax.ws.rs.Produces;
56+
import javax.ws.rs.core.Context;
57+
import javax.ws.rs.core.Response;
58+
59+
/**
60+
* Class containing the REST API for API based authentication.
61+
*/
62+
@Path("/authn")
63+
public class ApiAuthnEndpoint {
64+
65+
private final AuthenticationService authenticationService = new AuthenticationService();
66+
private final OAuth2AuthzEndpoint oAuth2AuthzEndpoint = new OAuth2AuthzEndpoint();
67+
private static final String AUTHENTICATOR = "authenticator";
68+
private static final String IDP = "idp";
69+
private static final Log LOG = LogFactory.getLog(ApiAuthnEndpoint.class);
70+
private static final ApiAuthnHandler API_AUTHN_HANDLER = new ApiAuthnHandler();
71+
72+
@POST
73+
@Path("/")
74+
@Consumes("application/json")
75+
@Produces("application/json")
76+
public Response handleAuthentication(@Context HttpServletRequest request, @Context HttpServletResponse response,
77+
String payload) {
78+
79+
try {
80+
AuthRequest authRequest = buildAuthRequest(payload);
81+
AuthServiceRequest authServiceRequest = getAuthServiceRequest(request, response, authRequest);
82+
AuthServiceResponse authServiceResponse = authenticationService.handleAuthentication(authServiceRequest);
83+
84+
switch (authServiceResponse.getFlowStatus()) {
85+
case INCOMPLETE:
86+
return handleIncompleteAuthResponse(authServiceResponse);
87+
case SUCCESS_COMPLETED:
88+
return handleSuccessCompletedAuthResponse(request, response, authServiceResponse);
89+
case FAIL_INCOMPLETE:
90+
return handleFailAuthResponse(authServiceResponse);
91+
case FAIL_COMPLETED:
92+
return handleFailAuthResponse(authServiceResponse);
93+
default:
94+
throw new AuthServiceException("Unknown flow status: " + authServiceResponse.getFlowStatus());
95+
}
96+
97+
} catch (AuthServiceClientException | InvalidRequestParentException e) {
98+
if (LOG.isDebugEnabled()) {
99+
LOG.debug("Client error while handling authentication request.", e);
100+
}
101+
return buildOAuthInvalidRequestError(e.getMessage());
102+
} catch (AuthServiceException | URISyntaxException e) {
103+
LOG.error("Error while handling authentication request.", e);
104+
return buildOAuthServerError();
105+
}
106+
}
107+
108+
private AuthRequest buildAuthRequest(String payload) throws AuthServiceClientException {
109+
110+
try {
111+
ObjectMapper objectMapper = new ObjectMapper();
112+
return objectMapper.readValue(payload, AuthRequest.class);
113+
} catch (JsonProcessingException e) {
114+
// Throwing a client exception here as the exception can occur due to a malformed request.
115+
throw new AuthServiceClientException(e.getMessage());
116+
}
117+
}
118+
119+
private Response buildResponse(AuthResponse response) {
120+
121+
ObjectMapper objectMapper = new ObjectMapper();
122+
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
123+
String jsonString = null;
124+
try {
125+
jsonString = objectMapper.writeValueAsString(response);
126+
} catch (JsonProcessingException e) {
127+
throw new RuntimeException(e);
128+
}
129+
return Response.ok().entity(jsonString).build();
130+
}
131+
132+
private AuthServiceRequest getAuthServiceRequest(HttpServletRequest request, HttpServletResponse response,
133+
AuthRequest authRequest) throws AuthServiceClientException {
134+
135+
Map<String, String[]> params = new HashMap<>();
136+
params.put(OAuthConstants.SESSION_DATA_KEY, new String[]{authRequest.getFlowId()});
137+
138+
String authenticatorId = authRequest.getSelectedAuthenticator().getAuthenticatorId();
139+
if (authenticatorId != null) {
140+
String decodedAuthenticatorId = base64URLDecode(authenticatorId);
141+
String[] authenticatorIdSplit = decodedAuthenticatorId.split(OAuthConstants.AUTHENTICATOR_IDP_SPLITTER);
142+
143+
if (authenticatorIdSplit.length == 2) {
144+
params.put(AUTHENTICATOR, new String[]{authenticatorIdSplit[0]});
145+
params.put(IDP, new String[]{authenticatorIdSplit[1]});
146+
} else {
147+
throw new AuthServiceClientException("Provided authenticator id: " + authenticatorId + " is invalid.");
148+
}
149+
} else {
150+
throw new AuthServiceClientException("Authenticator id is not provided.");
151+
}
152+
153+
Map<String, String[]> authParams = authRequest.getSelectedAuthenticator().getParams().entrySet().stream()
154+
.collect(Collectors.toMap(Map.Entry::getKey, e -> new String[]{e.getValue()}));
155+
params.putAll(authParams);
156+
157+
return new AuthServiceRequest(request, response, params);
158+
}
159+
160+
private String base64URLDecode(String value) {
161+
162+
return new String(
163+
Base64.getUrlDecoder().decode(value),
164+
StandardCharsets.UTF_8);
165+
}
166+
167+
private Response handleSuccessCompletedAuthResponse(HttpServletRequest request, HttpServletResponse response,
168+
AuthServiceResponse authServiceResponse) throws
169+
InvalidRequestParentException, URISyntaxException {
170+
171+
String callerSessionDataKey = authServiceResponse.getSessionDataKey();
172+
173+
Map<String, List<String>> internalParamsList = new HashMap<>();
174+
internalParamsList.put(OAuthConstants.SESSION_DATA_KEY, Collections.singletonList(callerSessionDataKey));
175+
OAuthRequestWrapper internalRequest = new OAuthRequestWrapper(request, internalParamsList);
176+
internalRequest.setInternalRequest(true);
177+
178+
return oAuth2AuthzEndpoint.authorize(internalRequest, response);
179+
}
180+
181+
private Response handleIncompleteAuthResponse(AuthServiceResponse authServiceResponse) throws AuthServiceException {
182+
183+
AuthResponse authResponse = API_AUTHN_HANDLER.handleResponse(authServiceResponse);
184+
return buildResponse(authResponse);
185+
}
186+
187+
private Response handleFailAuthResponse(AuthServiceResponse authServiceResponse) {
188+
189+
String errorMsg = "Unhandled flow status: " + authServiceResponse.getFlowStatus();
190+
if (authServiceResponse.getErrorInfo().isPresent()) {
191+
AuthServiceErrorInfo errorInfo = authServiceResponse.getErrorInfo().get();
192+
errorMsg = errorInfo.getErrorCode() + " - " + errorInfo.getErrorMessage();
193+
}
194+
Map<String, String> params = new HashMap<>();
195+
params.put(OAuthConstants.OAUTH_ERROR, OAuth2ErrorCodes.SERVER_ERROR);
196+
params.put(OAuthConstants.OAUTH_ERROR_DESCRIPTION, errorMsg);
197+
String jsonString = new Gson().toJson(params);
198+
return Response.status(HttpServletResponse.SC_INTERNAL_SERVER_ERROR).entity(jsonString).build();
199+
}
200+
201+
private Response buildOAuthInvalidRequestError(String errorMessage) {
202+
203+
Map<String, String> params = new HashMap<>();
204+
params.put(OAuthConstants.OAUTH_ERROR, OAuth2ErrorCodes.INVALID_REQUEST);
205+
params.put(OAuthConstants.OAUTH_ERROR_DESCRIPTION, errorMessage);
206+
String jsonString = new Gson().toJson(params);
207+
return Response.status(HttpServletResponse.SC_INTERNAL_SERVER_ERROR).entity(jsonString).build();
208+
}
209+
210+
private Response buildOAuthServerError() {
211+
212+
Map<String, String> params = new HashMap<>();
213+
params.put(OAuthConstants.OAUTH_ERROR, OAuth2ErrorCodes.SERVER_ERROR);
214+
params.put(OAuthConstants.OAUTH_ERROR_DESCRIPTION, "Server error occurred while performing authentication.");
215+
String jsonString = new Gson().toJson(params);
216+
return Response.status(HttpServletResponse.SC_INTERNAL_SERVER_ERROR).entity(jsonString).build();
217+
}
218+
}

0 commit comments

Comments
 (0)