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

[server] add vscode(-insiders) ouath2 clients #6272

Merged
merged 1 commit into from
Apr 12, 2022
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
2 changes: 1 addition & 1 deletion components/gitpod-db/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
],
"dependencies": {
"@gitpod/gitpod-protocol": "0.1.5",
"@jmondi/oauth2-server": "^1.1.0",
"@jmondi/oauth2-server": "^2.2.2",
"mysql": "^2.18.1",
"reflect-metadata": "^0.1.13",
"the-big-username-blacklist": "^1.5.2",
Expand Down
4 changes: 2 additions & 2 deletions components/gitpod-db/src/typeorm/auth-code-repository-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class AuthCodeRepositoryDB implements OAuthAuthCodeRepository {
return (await this.getEntityManager()).getRepository<DBOAuthAuthCodeEntry>(DBOAuthAuthCodeEntry);
}

public async getByIdentifier(authCodeCode: string): Promise<OAuthAuthCode> {
public async getByIdentifier(authCodeCode: string): Promise<DBOAuthAuthCodeEntry> {
const authCodeRepo = await this.getOauthAuthCodeRepo();
let authCodes = await authCodeRepo.find({ code: authCodeCode });
authCodes = authCodes.filter((te) => new Date(te.expiresAt).getTime() > Date.now());
Expand All @@ -54,7 +54,7 @@ export class AuthCodeRepositoryDB implements OAuthAuthCodeRepository {
scopes: scopes,
};
}
public async persist(authCode: OAuthAuthCode): Promise<void> {
public async persist(authCode: DBOAuthAuthCodeEntry): Promise<void> {
const authCodeRepo = await this.getOauthAuthCodeRepo();
authCodeRepo.save(authCode);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* See License-AGPL.txt in the project root for license information.
*/

import { OAuthAuthCode, OAuthClient, OAuthScope } from "@jmondi/oauth2-server";
import { CodeChallengeMethod, OAuthAuthCode, OAuthClient, OAuthScope } from "@jmondi/oauth2-server";
import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import { Transformer } from "../transformer";
import { DBUser } from "./db-user";
Expand Down Expand Up @@ -38,7 +38,7 @@ export class DBOAuthAuthCodeEntry implements OAuthAuthCode {
type: "varchar",
length: 10,
})
codeChallengeMethod: string;
codeChallengeMethod: CodeChallengeMethod

@Column({
type: "timestamp",
Expand Down
2 changes: 1 addition & 1 deletion components/gitpod-db/src/typeorm/user-db-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ export class TypeORMUserDBImpl implements UserDB {
} else {
var user: MaybeUser;
if (accessToken.user) {
user = await this.findUserById(accessToken.user.id);
user = await this.findUserById(accessToken.user.id.toString());
}
dbToken = {
tokenHash,
Expand Down
2 changes: 1 addition & 1 deletion components/proxy/conf/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ https://{$GITPOD_DOMAIN} {
@codesync path /code-sync*
handle @codesync {
gitpod.cors_origin {
base_domain {$GITPOD_DOMAIN}
any_domain true
geropl marked this conversation as resolved.
Show resolved Hide resolved
}

import compression
Expand Down
18 changes: 16 additions & 2 deletions components/proxy/plugins/corsorigin/cors_origin.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func init() {

// CorsOrigin implements an HTTP handler that generates a valid CORS Origin value
type CorsOrigin struct {
AnyDomain bool `json:"any_domain,omitempty"`
BaseDomain string `json:"base_domain,omitempty"`
Debug bool `json:"debug,omitempty"`
}
Expand All @@ -50,8 +51,14 @@ var (

// ServeHTTP implements caddyhttp.MiddlewareHandler.
func (m CorsOrigin) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
var allowedOrigins []string
if m.AnyDomain {
allowedOrigins = []string{"*"}
} else {
allowedOrigins = []string{"*." + m.BaseDomain}
}
c := cors.New(cors.Options{
AllowedOrigins: []string{"*." + m.BaseDomain},
AllowedOrigins: allowedOrigins,
AllowedMethods: allowedMethods,
AllowedHeaders: allowedHeaders,
ExposedHeaders: exposeHeaders,
Expand Down Expand Up @@ -84,6 +91,13 @@ func (m *CorsOrigin) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}

switch key {
case "any_domain":
b, err := strconv.ParseBool(value)
if err != nil {
return d.Errf("invalid boolean value for subdirective any_domain '%s'", value)
}

m.AnyDomain = b
case "base_domain":
m.BaseDomain = value
case "debug":
Expand All @@ -98,7 +112,7 @@ func (m *CorsOrigin) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
}

if m.BaseDomain == "" {
if !m.AnyDomain && m.BaseDomain == "" {
return fmt.Errorf("Please configure the base_domain subdirective")
}

Expand Down
2 changes: 1 addition & 1 deletion components/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"@gitpod/ws-manager": "0.1.5",
"@google-cloud/storage": "^5.6.0",
"@improbable-eng/grpc-web-node-http-transport": "^0.14.0",
"@jmondi/oauth2-server": "^1.1.0",
"@jmondi/oauth2-server": "^2.2.2",
"@octokit/rest": "18.6.1",
"@probot/get-private-key": "^1.1.1",
"amqplib": "^0.8.0",
Expand Down
20 changes: 20 additions & 0 deletions components/server/src/oauth-server/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,30 @@ const jetBrainsGateway: OAuthClient = {
],
};

function createVSCodeClient(protocol: "vscode" | "vscode-insiders"): OAuthClient {
return {
id: protocol + "-" + "gitpod",
name: `VS Code${protocol === "vscode-insiders" ? " Insiders" : ""}: Gitpod extension`,
redirectUris: [protocol + "://gitpod.gitpod-desktop/complete-gitpod-auth"],
allowedGrants: ["authorization_code"],
scopes: [
{ name: "function:getGitpodTokenScopes" },
{ name: "function:getLoggedInUser" },
{ name: "function:accessCodeSyncStorage" },
{ name: "resource:default" },
],
};
}

const vscode = createVSCodeClient("vscode");
const vscodeInsiders = createVSCodeClient("vscode-insiders");

export const inMemoryDatabase: InMemory = {
clients: {
[localClient.id]: localClient,
[jetBrainsGateway.id]: jetBrainsGateway,
[vscode.id]: vscode,
[vscodeInsiders.id]: vscodeInsiders,
},
tokens: {},
scopes: {},
Expand Down
67 changes: 31 additions & 36 deletions components/server/src/oauth-server/oauth-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import { AuthCodeRepositoryDB } from "@gitpod/gitpod-db/lib/typeorm/auth-code-re
import { UserDB } from "@gitpod/gitpod-db/lib/user-db";
import { User } from "@gitpod/gitpod-protocol";
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
import { OAuthException, OAuthRequest, OAuthResponse } from "@jmondi/oauth2-server";
import { OAuthRequest, OAuthResponse } from "@jmondi/oauth2-server";
import { handleExpressResponse, handleExpressError } from "@jmondi/oauth2-server/dist/adapters/express";
import * as express from "express";
import { inject, injectable } from "inversify";
import { URL } from "url";
import { Config } from "../config";
import { clientRepository, createAuthorizationServer } from "./oauth-authorization-server";

Expand Down Expand Up @@ -58,10 +60,33 @@ export class OAuthController {
const rt = req.query.redirect_uri?.toString();
if (!rt || !rt.startsWith("http://127.0.0.1:")) {
log.error(`/oauth/authorize: invalid returnTo URL: "${rt}"`);
}

const client = await clientRepository.getByIdentifier(clientID);
if (client) {
if (typeof req.query.redirect_uri !== "string") {
log.error(req.query.redirect_uri ? "Missing redirect URI" : "Invalid format of redirect URI");
res.sendStatus(400);
return false;
}

const normalizedRedirectUri = new URL(req.query.redirect_uri);
normalizedRedirectUri.search = "";

if (!client.redirectUris.some((u) => new URL(u).toString() === normalizedRedirectUri.toString())) {
log.error(`/oauth/authorize: invalid returnTo URL: "${req.query.redirect_uri}"`);
res.sendStatus(400);
return false;
}
} else {
log.error(`/oauth/authorize unknown client id: "${clientID}"`);
res.sendStatus(400);
return false;
}
res.redirect(`${rt}/?approved=no`);

const redirectUri = new URL(req.query.redirect_uri);
redirectUri.searchParams.append("approved", "no");
res.redirect(redirectUri.toString());
return false;
} else if (wasApproved == "yes") {
const additionalData = (user.additionalData = user.additionalData || {});
Expand Down Expand Up @@ -133,53 +158,23 @@ export class OAuthController {

// Return the HTTP redirect response
const oauthResponse = await authorizationServer.completeAuthorizationRequest(authRequest);
return handleResponse(req, res, oauthResponse);
return handleExpressResponse(res, oauthResponse);
} catch (e) {
handleError(e, res);
handleExpressError(e, res);
}
});

router.post("/oauth/token", async (req: express.Request, res: express.Response) => {
const response = new OAuthResponse(res);
try {
const oauthResponse = await authorizationServer.respondToAccessTokenRequest(req, response);
return handleResponse(req, res, oauthResponse);
return handleExpressResponse(res, oauthResponse);
} catch (e) {
handleError(e, res);
handleExpressError(e, res);
return;
}
});

function handleError(e: Error | undefined, res: express.Response) {
if (e instanceof OAuthException) {
res.status(e.status);
res.send({
status: e.status,
message: e.message,
stack: e.stack,
});
return;
}
// Generic error
res.status(500);
res.send({
err: e,
});
}

function handleResponse(req: express.Request, res: express.Response, response: OAuthResponse) {
if (response.status === 302) {
if (!response.headers.location) {
throw new Error("missing redirect location");
}
res.set(response.headers);
res.redirect(response.headers.location);
} else {
res.set(response.headers);
res.status(response.status).send(response.body);
}
}

return router;
}
}
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1727,10 +1727,10 @@
"@types/yargs" "^16.0.0"
chalk "^4.0.0"

"@jmondi/oauth2-server@^1.1.0":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@jmondi/oauth2-server/-/oauth2-server-1.1.1.tgz#cb2530c17e2c7db3cc632bb68ec0f38a54c7ae1c"
integrity sha512-my3776n6TDsJQJ+nONG52VNgTQ7veH9lo4kb/AAWt9Rko6VBuMxOb/KxcYdkDrpOznJ036+tVveuhY7zSJjGYg==
"@jmondi/oauth2-server@^2.2.2":
version "2.2.2"
resolved "https://registry.yarnpkg.com/@jmondi/oauth2-server/-/oauth2-server-2.2.2.tgz#e99b6edcd068c1a58423e2e8e94eeb0acb048b39"
integrity sha512-U9038EvDQJwc6SUxGjNfP1nhcyIzUuo4MLDBjWEp4ieuoyMYFBlMtmUbXcIEvvlwWQSXneUm7+TcTBcNHKrE8w==
dependencies:
jsonwebtoken "^8.5.1"
ms "^2.1.3"
Expand Down