Skip to content

Commit 01163a9

Browse files
committed
WIP
1 parent c0a7be3 commit 01163a9

File tree

4 files changed

+46
-16
lines changed

4 files changed

+46
-16
lines changed

components/server/src/auth/authenticator.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { UserService } from "../user/user-service";
1818
import { AuthFlow, AuthProvider } from "./auth-provider";
1919
import { HostContextProvider } from "./host-context-provider";
2020
import { SignInJWT } from "./jwt";
21+
import { getReturnToParamWithSafeBaseDomain } from "../express-util";
2122

2223
@injectable()
2324
export class Authenticator {
@@ -239,6 +240,14 @@ export class Authenticator {
239240
return;
240241
}
241242

243+
// Validate returnTo URL
244+
const valid = validateAuthorizeReturnToUrl(returnTo, this.config, authProvider);
245+
if (!valid) {
246+
log.info(`Bad request: invalid returnTo URL.`, { "authorize-flow": true });
247+
res.redirect(this.getSorryUrl(`Bad request: invalid returnTo URL.`));
248+
return;
249+
}
250+
242251
// For non-verified org auth provider, ensure user is an owner of the org
243252
if (!authProvider.info.verified && authProvider.info.organizationId) {
244253
const member = await this.teamDb.findTeamMembership(user.id, authProvider.info.organizationId);
@@ -322,3 +331,9 @@ export class Authenticator {
322331
return this.config.hostUrl.asSorry(message).toString();
323332
}
324333
}
334+
335+
// Whether the passed URL is a valid
336+
export function validateAuthorizeReturnToUrl(returnTo: string, config: Config, authProvider: AuthProvider): boolean {
337+
// Constraints
338+
getReturnToParamWithSafeBaseDomain(returnTo, config.hostUrl.url);
339+
}

components/server/src/express-util.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,26 @@ export function toClientHeaderFields(expressReq: express.Request): ClientHeaderF
159159
clientRegion: takeFirst(expressReq.headers["x-glb-client-region"]),
160160
};
161161
}
162+
163+
export function getReturnToParamWithSafeBaseDomain(
164+
returnToURL: string | undefined,
165+
configuredBaseDomain: URL,
166+
): string | undefined {
167+
function urlStartsWith(url: string, prefixUrl: string): boolean {
168+
prefixUrl += prefixUrl.endsWith("/") ? "" : "/";
169+
return url.toLowerCase().startsWith(prefixUrl.toLowerCase());
170+
}
171+
172+
if (!returnToURL) {
173+
return undefined;
174+
}
175+
176+
if (
177+
urlStartsWith(returnToURL, configuredBaseDomain.toString()) ||
178+
urlStartsWith(returnToURL, "https://www.gitpod.io")
179+
) {
180+
return returnToURL;
181+
}
182+
183+
return;
184+
}

components/server/src/user/user-controller.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { Permission } from "@gitpod/gitpod-protocol/lib/permission";
1717
import { parseWorkspaceIdFromHostname } from "@gitpod/gitpod-protocol/lib/util/parse-workspace-id";
1818
import { SessionHandler } from "../session-handler";
1919
import { URL } from "url";
20-
import { getRequestingClientInfo } from "../express-util";
20+
import { getRequestingClientInfo, getReturnToParamWithSafeBaseDomain } from "../express-util";
2121
import { GitpodToken, GitpodTokenType, User } from "@gitpod/gitpod-protocol";
2222
import { HostContextProvider } from "../auth/host-context-provider";
2323
import { reportJWTCookieIssued } from "../prometheus-metrics";
@@ -37,7 +37,6 @@ import { UserService } from "./user-service";
3737
import { WorkspaceService } from "../workspace/workspace-service";
3838
import { runWithSubjectId } from "../util/request-context";
3939
import { SubjectId } from "../auth/subject-id";
40-
import { TrustedValue } from "@gitpod/gitpod-protocol/lib/util/scrubbing";
4140

4241
export const ServerFactory = Symbol("ServerFactory");
4342
export type ServerFactory = () => GitpodServerImpl;
@@ -615,20 +614,7 @@ export class UserController {
615614
protected getSafeReturnToParam(req: express.Request) {
616615
// @ts-ignore Type 'ParsedQs' is not assignable
617616
const returnToURL: string | undefined = req.query.redirect || req.query.returnTo;
618-
if (!returnToURL) {
619-
log.debug("Empty redirect URL");
620-
return;
621-
}
622-
623-
if (
624-
this.urlStartsWith(returnToURL, this.config.hostUrl.toString()) ||
625-
this.urlStartsWith(returnToURL, "https://www.gitpod.io")
626-
) {
627-
return returnToURL;
628-
}
629-
630-
log.debug("The redirect URL does not match", { query: new TrustedValue(req.query).value });
631-
return;
617+
return getReturnToParamWithSafeBaseDomain(returnToURL, this.config.hostUrl.url);
632618
}
633619

634620
private createGitpodServer(user: User, resourceGuard: ResourceAccessGuard) {

components/server/src/util/featureflags.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,9 @@ export async function getFeatureFlagEnableExperimentalJBTB(userId: string): Prom
1111
user: { id: userId },
1212
});
1313
}
14+
15+
export async function getFeatureFlagEnforceAuthorizeStateValidation(userId: string): Promise<boolean> {
16+
return getExperimentsClientForBackend().getValueAsync("enforce_authorize_state_validation", false, {
17+
user: { id: userId },
18+
});
19+
}

0 commit comments

Comments
 (0)