Skip to content

Commit

Permalink
Dynamically compute OpenID redirectUri from proxy HTTP headers (#929)
Browse files Browse the repository at this point in the history
Signed-off-by: Jean-Christian Simonetti <[email protected]>

Co-authored-by: Peter Nied <[email protected]>
Co-authored-by: Chang Liu <[email protected]>
  • Loading branch information
3 people authored Jun 8, 2022
1 parent 2de0e4e commit c19b01f
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 10 deletions.
84 changes: 83 additions & 1 deletion server/auth/types/openid/helper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* permissions and limitations under the License.
*/

import { composeLogoutUrl } from './helper';
import { composeLogoutUrl, getRootUrl } from './helper';

describe('test OIDC helper utility', () => {
test('test compose logout url', () => {
Expand Down Expand Up @@ -55,4 +55,86 @@ describe('test OIDC helper utility', () => {
composeLogoutUrl(customLogoutUrl, idpEndSessionUrl, additionalQuery)
);
});

test('test root url when trusted header unset', () => {
const config = {
openid: {
trust_dynamic_headers: false,
},
};

const core = {
http: {
getServerInfo: () => {
return {
hostname: 'server.com',
port: 80,
protocol: 'http',
};
},
},
};

const request = {
headers: {
'x-forwarded-host': 'dashboards.com:443',
'x-forwarded-proto': 'https',
},
};

expect('http://server.com:80').toEqual(getRootUrl(config, core, request));
});

test('test root url when trusted header set', () => {
const config = {
openid: {
trust_dynamic_headers: true,
},
};

const core = {
http: {
getServerInfo: () => {
return {
hostname: 'server.com',
port: 80,
protocol: 'http',
};
},
},
};

const request = {
headers: {
'x-forwarded-host': 'dashboards.com:443',
'x-forwarded-proto': 'https',
},
};

expect('https://dashboards.com:443').toEqual(getRootUrl(config, core, request));
});

test('test root url when trusted header set and no HTTP header', () => {
const config = {
openid: {
trust_dynamic_headers: true,
},
};

const core = {
http: {
getServerInfo: () => {
return {
hostname: 'server.com',
port: 80,
protocol: 'http',
};
},
},
};

const request = { headers: {} };

expect('http://server.com:80').toEqual(getRootUrl(config, core, request));
});
});
39 changes: 33 additions & 6 deletions server/auth/types/openid/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import wreck from '@hapi/wreck';
import { parse, stringify } from 'querystring';
import { CoreSetup } from 'opensearch-dashboards/server';
import { SecurityPluginConfigType } from '../../..';
import { OpenSearchDashboardsRequest } from '../../../../../../src/core/server';

export function parseTokenResponse(payload: Buffer) {
const payloadString = payload.toString();
Expand All @@ -30,19 +31,45 @@ export function parseTokenResponse(payload: Buffer) {
return parse(payloadString);
}

export function getBaseRedirectUrl(config: SecurityPluginConfigType, core: CoreSetup): string {
export function getRootUrl(
config: SecurityPluginConfigType,
core: CoreSetup,
request: OpenSearchDashboardsRequest
): string {
const host = core.http.getServerInfo().hostname;
const port = core.http.getServerInfo().port;
let protocol = core.http.getServerInfo().protocol;
let httpHost = `${host}:${port}`;

if (config.openid?.trust_dynamic_headers) {
const xForwardedHost = (request.headers['x-forwarded-host'] as string) || undefined;
const xForwardedProto = (request.headers['x-forwarded-proto'] as string) || undefined;
if (xForwardedHost) {
httpHost = xForwardedHost;
}
if (xForwardedProto) {
protocol = xForwardedProto;
}
}

return `${protocol}://${httpHost}`;
}

export function getBaseRedirectUrl(
config: SecurityPluginConfigType,
core: CoreSetup,
request: OpenSearchDashboardsRequest
): string {
if (config.openid?.base_redirect_url) {
const baseRedirectUrl = config.openid.base_redirect_url;
return baseRedirectUrl.endsWith('/') ? baseRedirectUrl.slice(0, -1) : baseRedirectUrl;
}

const host = core.http.getServerInfo().hostname;
const port = core.http.getServerInfo().port;
const protocol = core.http.getServerInfo().protocol;
const rootUrl = getRootUrl(config, core, request);
if (core.http.basePath.serverBasePath) {
return `${protocol}://${host}:${port}${core.http.basePath.serverBasePath}`;
return `${rootUrl}${core.http.basePath.serverBasePath}`;
}
return `${protocol}://${host}:${port}`;
return rootUrl;
}

export async function callTokenEndpoint(
Expand Down
10 changes: 7 additions & 3 deletions server/auth/types/openid/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,11 @@ export class OpenIdAuthRoutes {
const query: any = {
client_id: this.config.openid?.client_id,
response_type: 'code',
redirect_uri: `${getBaseRedirectUrl(this.config, this.core)}/auth/openid/login`,
redirect_uri: `${getBaseRedirectUrl(
this.config,
this.core,
request
)}/auth/openid/login`,
state: nonce,
scope: this.openIdAuthConfig.scope,
};
Expand Down Expand Up @@ -133,7 +137,7 @@ export class OpenIdAuthRoutes {
const query: any = {
grant_type: 'authorization_code',
code: request.query.code,
redirect_uri: `${getBaseRedirectUrl(this.config, this.core)}/auth/openid/login`,
redirect_uri: `${getBaseRedirectUrl(this.config, this.core, request)}/auth/openid/login`,
client_id: clientId,
client_secret: clientSecret,
};
Expand Down Expand Up @@ -192,7 +196,7 @@ export class OpenIdAuthRoutes {
// authHeaderValue is the bearer header, e.g. "Bearer <auth_token>"
const token = cookie?.credentials.authHeaderValue.split(' ')[1]; // get auth token
const logoutQueryParams = {
post_logout_redirect_uri: getBaseRedirectUrl(this.config, this.core),
post_logout_redirect_uri: getBaseRedirectUrl(this.config, this.core, request),
id_token_hint: token,
};

Expand Down
1 change: 1 addition & 0 deletions server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export const configSchema = schema.object({
root_ca: schema.string({ defaultValue: '' }),
verify_hostnames: schema.boolean({ defaultValue: true }),
refresh_tokens: schema.boolean({ defaultValue: true }),
trust_dynamic_headers: schema.boolean({ defaultValue: false }),
})
),
proxycache: schema.maybe(
Expand Down

0 comments on commit c19b01f

Please sign in to comment.