Skip to content

Commit

Permalink
feat: add atlassian oauth app type and defaults
Browse files Browse the repository at this point in the history
Signed-off-by: Nick Hale <[email protected]>
  • Loading branch information
njhale committed Dec 16, 2024
1 parent e4a854b commit ffdd2c6
Show file tree
Hide file tree
Showing 12 changed files with 210 additions and 9 deletions.
1 change: 1 addition & 0 deletions apiclient/types/oauthapp.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package types

const (
OAuthAppTypeAtlassian OAuthAppType = "atlassian"
OAuthAppTypeMicrosoft365 OAuthAppType = "microsoft365"
OAuthAppTypeSlack OAuthAppType = "slack"
OAuthAppTypeNotion OAuthAppType = "notion"
Expand Down
8 changes: 8 additions & 0 deletions pkg/gateway/server/oauth_apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,14 @@ func (s *Server) authorizeOAuthApp(apiContext api.Context) error {
q.Set("optional_scope", app.Spec.Manifest.OptionalScope)
}

// Atlassian requires the audience and prompt parameters to be set.
// See https://developer.atlassian.com/cloud/jira/platform/oauth-2-3lo-apps/#1--direct-the-user-to-the-authorization-url-to-get-an-authorization-code
// for details.
if app.Spec.Manifest.Type == types2.OAuthAppTypeAtlassian {
q.Set("audience", "api.atlassian.com")
q.Set("prompt", "consent")
}

// For Google: access_type=offline instructs Google to return a refresh token and an access token on the initial authorization.
// This can be used to refresh the access token when a user is not present at the browser
// prompt=consent instructs Google to show the consent screen every time the authorization flow happens so that we get a new refresh token.
Expand Down
6 changes: 6 additions & 0 deletions pkg/gateway/types/oauth_apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (
)

const (
AtlassianAuthorizeURL = "https://auth.atlassian.com/authorize"
AtlassianTokenURL = "https://auth.atlassian.com/oauth/token"

SlackAuthorizeURL = "https://slack.com/oauth/v2/authorize"
SlackTokenURL = "https://slack.com/api/oauth.v2.access"

Expand Down Expand Up @@ -44,6 +47,9 @@ func ValidateAndSetDefaultsOAuthAppManifest(r *types.OAuthAppManifest, create bo
}

switch r.Type {
case types.OAuthAppTypeAtlassian:
r.AuthURL = AtlassianAuthorizeURL
r.TokenURL = AtlassianTokenURL
case types.OAuthAppTypeMicrosoft365:
r.AuthURL = fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/authorize", r.TenantID)
r.TokenURL = fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/token", r.TenantID)
Expand Down
20 changes: 16 additions & 4 deletions ui/admin/app/components/oauth-apps/DeleteOAuthApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,28 @@ export function DeleteOAuthApp({
toast.success(`${spec.displayName} OAuth configuration deleted`);
});

const title = spec.noGatewayIntegration
? `Delete ${spec.displayName} OAuth`
: `Reset ${spec.displayName} OAuth to use Acorn Gateway`;

const description = spec.noGatewayIntegration
? `By clicking \`Delete\`, you will delete your ${spec.displayName} OAuth configuration.`
: `By clicking \`Reset\`, you will delete your custom ${spec.displayName} OAuth configuration and reset to use Acorn Gateway.`;

const buttonText = spec.noGatewayIntegration
? `Delete ${spec.displayName} OAuth`
: `Reset ${spec.displayName} OAuth to use Acorn Gateway`;

return (
<div className="flex gap-2">
<Tooltip open={getIsOpen()}>
<ConfirmationDialog
title={`Reset ${spec.displayName} OAuth to use Acorn Gateway`}
description={`By clicking \`Reset\`, you will delete your custom ${spec.displayName} OAuth configuration and reset to use Acorn Gateway.`}
title={title}
description={description}
onConfirm={deleteOAuthApp.execute}
confirmProps={{
variant: "destructive",
children: "Reset",
children: buttonText,
}}
>
<TooltipTrigger asChild>
Expand All @@ -54,7 +66,7 @@ export function DeleteOAuthApp({
{deleteOAuthApp.isLoading ? (
<LoadingSpinner className="w-4 h-4 mr-2" />
) : null}
Reset {spec.displayName} OAuth to use Acorn Gateway
{buttonText}
</Button>
</TooltipTrigger>
</ConfirmationDialog>
Expand Down
17 changes: 16 additions & 1 deletion ui/admin/app/components/oauth-apps/OAuthAppDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,22 @@ function EmptyContent({
spec: OAuthAppSpec;
onSuccess: () => void;
}) {
return (
return spec.noGatewayIntegration ? (
<div className="flex flex-col gap-2">
<TypographyP>
{spec.displayName} OAuth is not configured. You must configure
it to enable tools that interact with protected{" "}
{spec.displayName} APIs.
</TypographyP>

<TypographyP className="mb-4">
You can also configure {spec.displayName} OAuth by clicking the
button below.
</TypographyP>

<ConfigureOAuthApp type={spec.type} onSuccess={onSuccess} />
</div>
) : (
<div className="flex flex-col gap-2">
<TypographyP>
{spec.displayName} OAuth is currently enabled. No action is
Expand Down
15 changes: 13 additions & 2 deletions ui/admin/app/components/oauth-apps/OAuthAppTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,22 @@ export function OAuthAppTile({
organization.
</TooltipContent>
</Tooltip>
) : info.noGatewayIntegration ? (
<Tooltip>
<TooltipTrigger>
<Badge variant="secondary">
Not Configured
</Badge>
</TooltipTrigger>

<TooltipContent>
OAuth for {displayName} is not configured
</TooltipContent>
</Tooltip>
) : (
<Tooltip>
<TooltipTrigger>
<Badge variant="secondary">Default</Badge>
<Badge variant="secondary">Default Configured</Badge>
</TooltipTrigger>

<TooltipContent>
Expand All @@ -73,7 +85,6 @@ export function OAuthAppTile({

<OAuthAppDetail type={type} />
</CardHeader>

<CardContent className="flex-grow flex items-center justify-center">
<div className="h-[100px] flex justify-center items-center overflow-clip">
<img
Expand Down
9 changes: 8 additions & 1 deletion ui/admin/app/components/oauth-apps/OAuthAppTypeIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { NotionLogoIcon } from "@radix-ui/react-icons";
import { KeyIcon } from "lucide-react";
import { FaGithub, FaGoogle, FaMicrosoft, FaSlack } from "react-icons/fa";
import {
FaAtlassian,
FaGithub,
FaGoogle,
FaMicrosoft,
FaSlack,
} from "react-icons/fa";

import { OAuthProvider } from "~/lib/model/oauthApps/oauth-helpers";
import { cn } from "~/lib/utils";

const IconMap = {
[OAuthProvider.Atlassian]: FaAtlassian,
[OAuthProvider.GitHub]: FaGithub,
[OAuthProvider.Slack]: FaSlack,
[OAuthProvider.Google]: FaGoogle,
Expand Down
2 changes: 2 additions & 0 deletions ui/admin/app/lib/model/oauthApps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
OAuthAppSpec,
OAuthProvider,
} from "~/lib/model/oauthApps/oauth-helpers";
import { AtlassianOAuthApp } from "~/lib/model/oauthApps/providers/atlassian";
import { GitHubOAuthApp } from "~/lib/model/oauthApps/providers/github";
import { GoogleOAuthApp } from "~/lib/model/oauthApps/providers/google";
import { Microsoft365OAuthApp } from "~/lib/model/oauthApps/providers/microsoft365";
Expand All @@ -10,6 +11,7 @@ import { SlackOAuthApp } from "~/lib/model/oauthApps/providers/slack";
import { EntityMeta } from "~/lib/model/primitives";

export const OAuthAppSpecMap = {
[OAuthProvider.Atlassian]: AtlassianOAuthApp,
[OAuthProvider.GitHub]: GitHubOAuthApp,
[OAuthProvider.Google]: GoogleOAuthApp,
[OAuthProvider.Microsoft365]: Microsoft365OAuthApp,
Expand Down
2 changes: 2 additions & 0 deletions ui/admin/app/lib/model/oauthApps/oauth-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ZodObject, ZodType } from "zod";
import { ApiUrl } from "~/lib/routers/baseRouter";

export const OAuthProvider = {
Atlassian: "atlassian",
GitHub: "github",
Google: "google",
Microsoft365: "microsoft365",
Expand Down Expand Up @@ -42,6 +43,7 @@ export type OAuthAppSpec = {
disableConfiguration?: boolean;
disabledReason?: string;
invertDark?: boolean;
noGatewayIntegration?: boolean;
};

export function getOAuthLinks(type: OAuthProvider) {
Expand Down
102 changes: 102 additions & 0 deletions ui/admin/app/lib/model/oauthApps/providers/atlassian.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { z } from "zod";

import {
OAuthAppSpec,
OAuthFormStep,
getOAuthLinks,
} from "~/lib/model/oauthApps/oauth-helpers";
import { assetUrl } from "~/lib/utils";

const schema = z.object({
clientID: z.string().min(1, "Client ID is required"),
clientSecret: z.string().min(1, "Client Secret is required"),
});

const steps: OAuthFormStep<typeof schema.shape>[] = [
{
type: "markdown",
text:
"### Step 1: Create a new Atlassian OAuth 2.0 Integration\n" +
"- Navigate to [Create a new OAuth 2.0 (3LO) integration](https://developer.atlassian.com/console/myapps/create-3lo-app)\n" +
"- Enter `Acorn` as the integration name.\n" +
"- Click the checkbox to the terms and conditions.\n" +
"- Click the `Create` button.\n",
},
{
type: "markdown",
text:
"### Step 2: Configure OAuth Scopes\n" +
"Configure required OAuth Scopes by completing both sections below.\n",
},
{
type: "sectionGroup",
sections: [
{
title: "User identity API Scopes",
steps: [
{
type: "markdown",
text:
"- Navigate to the `Permissions` tab in the sidebar.\n" +
"- Click on the `Add` button for `User identity API`\n" +
"- Click on the `Configure` button for `User identity API`\n" +
"- Click on the `Edit Scopes` button to open the `Edit User identity API` modal.\n" +
"- Click the checkboxes to select the `read:me` and `read:account` scopes.\n" +
"- Click on the `Save` button.\n",
},
],
},
{
title: "Jira API Scopes",
steps: [
{
type: "markdown",
text:
"- Navigate to the `Permissions` tab in the sidebar.\n" +
"- Click on the `Add` button for `Jira API`\n" +
"- Click on the `Configure` button for `Jira API`\n" +
"- Click on the `Edit Scopes` button to open the `Edit Jira API` modal.\n" +
"- Click the checkboxes to select the `read:jira-work`, `write:jira-work`, and `read:jira-user` scopes.\n" +
"- Click on the `Save` button.\n",
},
],
},
],
},
{
type: "markdown",
text:
"### Step 3: Configure your OAuth Consent Screen\n" +
"- Navigate to the `Authorization` tab in the sidebar.\n" +
"- Click on the `Add` button for `OAuth 2.0 (3LO)`.\n" +
"- Enter the URL below in the `Callback URL` box and click on the `Save changes` button:\n",
},
{
type: "copy",
text: getOAuthLinks("atlassian").redirectURL,
},
{
type: "markdown",
text:
"### Step 4: Register your OAuth App credentials with Acorn\n" +
"- Navigate to the `Settings` tab in the sidebar.\n" +
"- Enter the `Client ID` and `Client Secret` from the `Authentication details` section into the fields below\n",
},
{ type: "input", input: "clientID", label: "Client ID" },
{
type: "input",
input: "clientSecret",
label: "Client Secret",
inputType: "password",
},
];

export const AtlassianOAuthApp = {
schema,
alias: "atlassian",
type: "atlassian",
displayName: "Atlassian",
logo: assetUrl("/assets/atlassian_logo.svg"),
steps: steps,
noGatewayIntegration: true,
} satisfies OAuthAppSpec;
2 changes: 1 addition & 1 deletion ui/admin/app/lib/model/oauthApps/providers/google.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const steps: OAuthFormStep<typeof schema.shape>[] = [
type: "markdown",
text:
"### Step 1: Create a new Google Project\n" +
"- Navigate to the [Credentials](https://console.cloud.google.com/apis/credentials) section of the APIs & Serivces page in your [Google API Dashboard](https://console.cloud.google.com).\n" +
"- Navigate to the [Credentials](https://console.cloud.google.com/apis/credentials) section of the APIs & Services page in your [Google API Dashboard](https://console.cloud.google.com).\n" +
"- If you already have a Google Project Setup, skip to Step 2.",
},
{
Expand Down
35 changes: 35 additions & 0 deletions ui/admin/public/assets/atlassian_logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit ffdd2c6

Please sign in to comment.