Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

checks for proxy headers to build redirect uri #2067

Merged
merged 8 commits into from
Sep 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package com.bakdata.conquery.apiv1;

import java.net.URI;
import java.net.URL;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriInfo;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
Expand All @@ -28,4 +32,25 @@ public static String getRequestURL(HttpServletRequest req) {
return StringUtils.removeEnd(host, req.getServletPath()); //remove prefix path
}

/**
* Resolves proxied paths to the requested original URI if necessary.
*/
public static URI getRequestURL(ContainerRequestContext req) {
final MultivaluedMap<String, String> headers = req.getHeaders();
if (headers.getFirst(AdditionalHeaders.HTTP_HEADER_REAL_HOST) != null) {
try {
return new URL(
headers.getFirst(AdditionalHeaders.HTTP_HEADER_REAL_PROTO),
headers.getFirst(AdditionalHeaders.HTTP_HEADER_REAL_HOST),
""
).toURI();
} catch (Exception e) {
log.warn("Failed to build response URL from X-Forward headers", e);
}
}

// Fallback: drop path and query, use only schema, authority and port
return req.getUriInfo().getRequestUriBuilder().replacePath(null).replaceQuery(null).build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ else if (tokenHeader != null) {
* @return a token
*/
@Nullable
private static String extractTokenFromHeader(ContainerRequestContext request) {
public static String extractTokenFromHeader(ContainerRequestContext request) {

final String header = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public void filter(ContainerRequestContext requestContext) throws IOException {
@Override
public void filter(ContainerRequestContext request, ContainerResponseContext response) throws IOException {
Cookie cookie = request.getCookies().get(ACCESS_TOKEN);
String token = request.getUriInfo().getQueryParameters().getFirst(ACCESS_TOKEN);
String token = request.getHeaderString(ACCESS_TOKEN);

// Set cookie only if a token is present
if (!Strings.isNullOrEmpty(token)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationToken;

import javax.annotation.Priority;
import javax.ws.rs.*;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.Response;
Expand All @@ -30,6 +31,7 @@
*/
@RequiredArgsConstructor
@Slf4j
@Priority(Priorities.AUTHENTICATION)
public class RedirectingAuthFilter extends AuthFilter<AuthenticationToken, User> {

/**
Expand Down Expand Up @@ -74,7 +76,7 @@ public void filter(ContainerRequestContext request) throws IOException {
}
else if (authenticatedRedirects.size() > 1) {
log.error("Multiple authenticated redirects generated. Only one should be possible");
throw new BadRequestException("The request triggered more than multi-step authentication schemas, which is not allowed.");
throw new BadRequestException("The request triggered more than one multi-step authentication schema, which is not allowed.");
}

// The request was not authenticated, nor was it a step towards an authentication, so we redirect the user to a login.
Expand All @@ -95,12 +97,8 @@ else if (authenticatedRedirects.size() > 1) {
throw new ServiceUnavailableException("No login schema configured");
}

if (loginRedirects.size() == 1) {
// There is only one login schema, redirect the user there
throw new RedirectionException(Response.seeOther(loginRedirects.get(0)).build());
}

// There are multiple login schemas, give the user a choice to choose between them
// Give the user a choice to choose between them. (If there is only one schema, still redirect the user there)
// to prevent too many redirects if there was a problem wit the authentication
throw new WebApplicationException(Response.ok(new UIView<>("logins.html.ftl", null, loginRedirects)).build());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class AuthenticationConfig {



public Cookie createAuthCookie(ContainerRequestContext request, String token) {
public NewCookie createAuthCookie(ContainerRequestContext request, String token) {
return new NewCookie(
AuthCookieFilter.ACCESS_TOKEN,
token,
Expand All @@ -41,6 +41,17 @@ public Cookie createAuthCookie(ContainerRequestContext request, String token) {
);
}

public static NewCookie expireAuthCookie() {
return new NewCookie(
AuthCookieFilter.ACCESS_TOKEN,
null,
"/",
null,
null,
0,
false);
}

@JsonIgnore
public AuthCookieFilter getAuthCookieFilter() {
return authCookieFilter.updateAndGet(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.bakdata.conquery.models.config.auth;

import com.bakdata.conquery.apiv1.RequestHelper;
import com.bakdata.conquery.commands.ManagerNode;
import com.bakdata.conquery.io.cps.CPSType;
import com.bakdata.conquery.io.jackson.Jackson;
import com.bakdata.conquery.models.auth.ConqueryAuthenticationRealm;
import com.bakdata.conquery.models.auth.oidc.JwtPkceVerifyingRealm;
import com.bakdata.conquery.models.auth.web.RedirectingAuthFilter;
import com.bakdata.conquery.resources.admin.AdminServlet;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
Expand Down Expand Up @@ -263,7 +265,7 @@ private URI initiateLogin(ContainerRequestContext request) {
URI uri = UriBuilder.fromUri(idpConfiguration.getAuthorizationEndpoint())
.queryParam("response_type","code")
.queryParam("client_id", client)
.queryParam("redirect_uri", request.getUriInfo().getRequestUriBuilder().replaceQuery("").build())
.queryParam("redirect_uri", UriBuilder.fromUri(RequestHelper.getRequestURL(request)).path(AdminServlet.ADMIN_UI).build())
.queryParam("scope","openid")
.queryParam("state", UUID.randomUUID()).build();
return uri;
Expand All @@ -287,7 +289,8 @@ private Response checkForAuthCallback(ContainerRequestContext request) {
return null;
}

final URI requestUri = request.getUriInfo().getAbsolutePath();
// Build the original redirect uri
final URI requestUri = UriBuilder.fromUri(RequestHelper.getRequestURL(request)).path(AdminServlet.ADMIN_UI).build();
log.info("Request URI: {}", requestUri);
final TokenRequest tokenRequest = new TokenRequest(
UriBuilder.fromUri(idpConfiguration.getTokenEndpoint()).build(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.UriBuilder;

import com.bakdata.conquery.apiv1.RequestHelper;
import com.bakdata.conquery.commands.ManagerNode;
import com.bakdata.conquery.io.cps.CPSType;
import com.bakdata.conquery.models.auth.ConqueryAuthenticationRealm;
Expand All @@ -18,6 +19,7 @@
import com.bakdata.conquery.models.auth.basic.UserAuthenticationManagementProcessor;
import com.bakdata.conquery.models.auth.web.RedirectingAuthFilter;
import com.bakdata.conquery.models.config.XodusConfig;
import com.bakdata.conquery.resources.admin.AdminServlet;
import com.bakdata.conquery.resources.admin.rest.UserAuthenticationManagementResource;
import com.bakdata.conquery.resources.unprotected.LoginResource;
import com.bakdata.conquery.resources.unprotected.TokenResource;
Expand Down Expand Up @@ -98,7 +100,7 @@ private Function<ContainerRequestContext,URI> loginProvider(DropwizardResourceCo
return (ContainerRequestContext request) -> {
URI uri = UriBuilder.fromPath(unprotectedAuthAdmin.getUrlPattern())
.path(LoginResource.class)
.queryParam(REDIRECT_URI, request.getUriInfo().getRequestUriBuilder().replaceQuery("").build())
.queryParam(REDIRECT_URI, UriBuilder.fromUri(RequestHelper.getRequestURL(request)).path(AdminServlet.ADMIN_UI).build())
.build();
return uri;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public class ResourceConstants {
public static final String ROLE_ID = "roleId";
public static final String GROUP_ID = "groupId";
public static final String SECONDARY_ID = "secondaryId";
public static final String ADMIN_SERVLET_PATH = "admin";
public static final String ADMIN_UI_SERVLET_PATH = "admin-ui";

public static final String FILE_EXTENTION_ARROW_FILE = "arrf";
public static final String FILE_EXTENTION_ARROW_STREAM = "arrs";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.bakdata.conquery.resources.admin;

import static com.bakdata.conquery.resources.ResourceConstants.*;
import java.util.Collections;

import com.bakdata.conquery.commands.ManagerNode;
Expand All @@ -8,7 +8,6 @@
import com.bakdata.conquery.io.jersey.IdParamConverter;
import com.bakdata.conquery.io.jersey.RESTServer;
import com.bakdata.conquery.models.auth.web.AuthCookieFilter;
import com.bakdata.conquery.models.auth.web.RedirectingAuthFilter;
import com.bakdata.conquery.resources.admin.rest.*;
import com.bakdata.conquery.resources.admin.ui.AdminUIResource;
import com.bakdata.conquery.resources.admin.ui.AuthOverviewUIResource;
Expand All @@ -35,6 +34,7 @@
@Slf4j
public class AdminServlet {

public static final String ADMIN_UI = "admin-ui";
private final AdminProcessor adminProcessor;
private final DropwizardResourceConfig jerseyConfig;
private final AdminDatasetProcessor adminDatasetProcessor;
Expand All @@ -48,8 +48,8 @@ public AdminServlet(ManagerNode manager) {

RESTServer.configure(manager.getConfig(), jerseyConfig);

manager.getEnvironment().admin().addServlet("admin", new ServletContainer(jerseyConfig)).addMapping("/admin/*");
manager.getEnvironment().admin().addServlet("admin-ui", new ServletContainer(jerseyConfigUI)).addMapping("/admin-ui/*");
manager.getEnvironment().admin().addServlet(ADMIN_SERVLET_PATH, new ServletContainer(jerseyConfig)).addMapping("/" + ADMIN_SERVLET_PATH + "/*");
manager.getEnvironment().admin().addServlet(ADMIN_UI_SERVLET_PATH, new ServletContainer(jerseyConfigUI)).addMapping("/" + ADMIN_UI_SERVLET_PATH + "/*");

jerseyConfig.register(new JacksonMessageBodyProvider(manager.getEnvironment().getObjectMapper()));
// freemarker support
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.bakdata.conquery.io.jersey.ExtraMimeTypes;
import com.bakdata.conquery.models.auth.entities.User;
import com.bakdata.conquery.models.common.Range;
import com.bakdata.conquery.models.config.auth.AuthenticationConfig;
import com.bakdata.conquery.models.identifiable.ids.specific.DatasetId;
import com.bakdata.conquery.models.jobs.Job;
import com.bakdata.conquery.models.jobs.JobManagerStatus;
Expand All @@ -17,6 +18,7 @@

import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
Expand Down Expand Up @@ -81,4 +83,11 @@ public Response cancelJob(@PathParam(JOB_ID) UUID jobId) {
public ImmutableMap<String, JobManagerStatus> getJobs() {
return processor.getJobs();
}

@GET
@Path("logout")
public Response logout() {
return Response.ok().cookie(AuthenticationConfig.expireAuthCookie()).build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.bakdata.conquery.models.jobs.JobManagerStatus;
import com.bakdata.conquery.models.messages.network.specific.CancelJobMessage;
import com.bakdata.conquery.models.worker.ShardNodeInformation;
import com.bakdata.conquery.resources.admin.AdminServlet;
import com.bakdata.conquery.resources.admin.rest.AdminProcessor;
import com.bakdata.conquery.resources.admin.rest.UIProcessor;
import com.bakdata.conquery.resources.admin.ui.model.UIView;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@
</#list>
</div>
</div>

<button type="button" class="btn btn-secondary" onclick="logout()">Logout</button>
</nav>

<div class="container">
Expand All @@ -102,6 +104,12 @@
}
)
}

function logout(){
event.preventDefault();
rest('/${ctx.staticUriElem.ADMIN_SERVLET_PATH}/logout')
.then(function () { location.reload() });
}

function postFile(event, url) {
event.preventDefault();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@
"path": "/users/{userId}/roles/{roleId}",
"clazz": "UserResource"
},
{
"method": "GET",
"path": "/logout",
"clazz": "AdminResource"
},
{
"method": "GET",
"path": "/jobs/",
Expand Down
22 changes: 11 additions & 11 deletions docs/Config JSON.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ No fields can be set for this type.

</p></details>

### JWT_PKCE_REALM<sup><sub><sup> [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/JwtPkceVerifyingRealmFactory.java#L41-L43)</sup></sub></sup>
### JWT_PKCE_REALM<sup><sub><sup> [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/JwtPkceVerifyingRealmFactory.java#L43-L45)</sup></sub></sup>
A realm that verifies oauth tokens using PKCE.

<details><summary>Details</summary><p>
Expand All @@ -69,14 +69,14 @@ Supported Fields:

| | Field | Type | Default | Example | Description |
| --- | --- | --- | --- | --- | --- |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/JwtPkceVerifyingRealmFactory.java#L56) | additionalTokenChecks | list of `String` | `[]` | | |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/JwtPkceVerifyingRealmFactory.java#L71-L74) | alternativeIdClaims | list of `String` | `[]` | | Which claims hold alternative Ids of the user in case the user name does not match a user. Pay attention, that the user must not be able to alter the value of any of these claims. |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/JwtPkceVerifyingRealmFactory.java#L50-L53) | client | `String` | `null` | | The client id is also used as the expected audience in the validated token. Ensure that the IDP is configured accordingly. |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/JwtPkceVerifyingRealmFactory.java#L65-L67) | idpConfiguration | `IdpConfiguration` | `null` | | See wellKnownEndpoint. |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/JwtPkceVerifyingRealmFactory.java#L59-L62) | wellKnownEndpoint | `URI` | `null` | | Either the wellKnownEndpoint from which an idpConfiguration can be obtained or the idpConfiguration must be supplied. If the idpConfiguration is given, the wellKnownEndpoint is ignored. |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/JwtPkceVerifyingRealmFactory.java#L58) | additionalTokenChecks | list of `String` | `[]` | | |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/JwtPkceVerifyingRealmFactory.java#L73-L76) | alternativeIdClaims | list of `String` | `[]` | | Which claims hold alternative Ids of the user in case the user name does not match a user. Pay attention, that the user must not be able to alter the value of any of these claims. |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/JwtPkceVerifyingRealmFactory.java#L52-L55) | client | `String` | `null` | | The client id is also used as the expected audience in the validated token. Ensure that the IDP is configured accordingly. |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/JwtPkceVerifyingRealmFactory.java#L67-L69) | idpConfiguration | `IdpConfiguration` | `null` | | See wellKnownEndpoint. |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/JwtPkceVerifyingRealmFactory.java#L61-L64) | wellKnownEndpoint | `URI` | `null` | | Either the wellKnownEndpoint from which an idpConfiguration can be obtained or the idpConfiguration must be supplied. If the idpConfiguration is given, the wellKnownEndpoint is ignored. |
</p></details>

### LOCAL_AUTHENTICATION<sup><sub><sup> [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/LocalAuthenticationConfig.java#L32)</sup></sub></sup>
### LOCAL_AUTHENTICATION<sup><sub><sup> [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/LocalAuthenticationConfig.java#L34)</sup></sub></sup>


<details><summary>Details</summary><p>
Expand All @@ -87,10 +87,10 @@ Supported Fields:

| | Field | Type | Default | Example | Description |
| --- | --- | --- | --- | --- | --- |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/LocalAuthenticationConfig.java#L54) | directory | `File` | `"./storage"` | | |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/LocalAuthenticationConfig.java#L44) | jwtDuration | `@MinDuration(value=1, unit=TimeUnit.MINUTES) Duration` | `"12 hours"` | | |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/LocalAuthenticationConfig.java#L38-L40) | passwordStoreConfig | [XodusConfig](#Type-XodusConfig) | | | Configuration for the password store. An encryption for the store it self might be set here. |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/LocalAuthenticationConfig.java#L47-L49) | storeName | `String` | `"authenticationStore"` | | The name of the folder the store lives in. |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/LocalAuthenticationConfig.java#L56) | directory | `File` | `"./storage"` | | |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/LocalAuthenticationConfig.java#L46) | jwtDuration | `@MinDuration(value=1, unit=TimeUnit.MINUTES) Duration` | `"12 hours"` | | |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/LocalAuthenticationConfig.java#L40-L42) | passwordStoreConfig | [XodusConfig](#Type-XodusConfig) | | | Configuration for the password store. An encryption for the store it self might be set here. |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/LocalAuthenticationConfig.java#L49-L51) | storeName | `String` | `"authenticationStore"` | | The name of the folder the store lives in. |
</p></details>

### OIDC_AUTHORIZATION_CODE_FLOW<sup><sub><sup> [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/OIDCAuthorizationCodeFlowRealmFactory.java#L11-L13)</sup></sub></sup>
Expand Down