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

feat: add servicenow oauth app integration #1064

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
3 changes: 2 additions & 1 deletion apiclient/types/oauthapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const (
OAuthAppTypeGitHub OAuthAppType = "github"
OAuthAppTypeGoogle OAuthAppType = "google"
OAuthAppTypeSalesforce OAuthAppType = "salesforce"
OAuthAppTypeServiceNow OAuthAppType = "servicenow"
OAuthAppTypeCustom OAuthAppType = "custom"
)

Expand Down Expand Up @@ -37,7 +38,7 @@ type OAuthAppManifest struct {
Integration string `json:"integration,omitempty"`
// Global indicates if the OAuth app is globally applied to all agents.
Global *bool `json:"global,omitempty"`
// This field is only used by Salesforce
// This field is only used by Salesforce and ServiceNow
InstanceURL string `json:"instanceURL,omitempty"`
}

Expand Down
22 changes: 21 additions & 1 deletion pkg/gateway/server/oauth_apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,8 @@ func (s *Server) callbackOAuthApp(apiContext api.Context) error {
return fmt.Errorf("failed to create token request: %w", err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if app.Spec.Manifest.Type != types2.OAuthAppTypeGoogle {
if app.Spec.Manifest.Type != types2.OAuthAppTypeGoogle &&
app.Spec.Manifest.Type != types2.OAuthAppTypeServiceNow {
req.SetBasicAuth(url.QueryEscape(app.Spec.Manifest.ClientID), url.QueryEscape(app.Spec.Manifest.ClientSecret))
}

Expand Down Expand Up @@ -470,6 +471,25 @@ func (s *Server) callbackOAuthApp(apiContext api.Context) error {
"GPTSCRIPT_SALESFORCE_URL": salesforceTokenResp.InstanceURL,
},
}
case types2.OAuthAppTypeServiceNow:
serviceNowTokenResp := new(types.ServiceNowOAuthTokenResponse)
if err := json.NewDecoder(resp.Body).Decode(serviceNowTokenResp); err != nil {
return fmt.Errorf("failed to parse token response: %w", err)
}

tokenResp = &types.OAuthTokenResponse{
State: state,
TokenType: serviceNowTokenResp.TokenType,
Scope: serviceNowTokenResp.Scope,
AccessToken: serviceNowTokenResp.AccessToken,
ExpiresIn: serviceNowTokenResp.ExpiresIn,
Ok: true, // Assuming true if no error is present
CreatedAt: time.Now(),
RefreshToken: serviceNowTokenResp.RefreshToken,
Extras: map[string]string{
"GPTSCRIPT_SALESFORCE_URL": app.Spec.Manifest.InstanceURL,
},
}
default:
if err := json.NewDecoder(resp.Body).Decode(tokenResp); err != nil {
return fmt.Errorf("failed to parse token response: %w", err)
Expand Down
27 changes: 27 additions & 0 deletions pkg/gateway/types/oauth_apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,25 @@ func ValidateAndSetDefaultsOAuthAppManifest(r *types.OAuthAppManifest, create bo
if err != nil {
errs = append(errs, err)
}
case types.OAuthAppTypeServiceNow:
serviceNowAuthorizeFragment := "oauth_auth.do"
serviceNowTokenFragment := "oauth_token.do"
instanceURL, err := url.Parse(r.InstanceURL)
if err != nil {
errs = append(errs, err)
}
if instanceURL.Scheme != "" {
instanceURL.Scheme = "https"
}

r.AuthURL, err = url.JoinPath(instanceURL.String(), serviceNowAuthorizeFragment)
if err != nil {
errs = append(errs, err)
}
r.TokenURL, err = url.JoinPath(instanceURL.String(), serviceNowTokenFragment)
if err != nil {
errs = append(errs, err)
}
}

if r.AuthURL == "" {
Expand Down Expand Up @@ -202,6 +221,14 @@ type SalesforceOAuthTokenResponse struct {
IssuedAt string `json:"issued_at"`
}

type ServiceNowOAuthTokenResponse struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
Scope string `json:"scope"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
}

type SlackOAuthTokenResponse struct {
Ok bool `json:"ok"`
Error string `json:"error"`
Expand Down
2 changes: 1 addition & 1 deletion pkg/storage/openapi/generated/openapi_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions ui/admin/app/components/oauth-apps/OAuthAppTypeIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const IconMap = {
[OAuthProvider.GitHub]: FaGithub,
[OAuthProvider.Slack]: FaSlack,
[OAuthProvider.Salesforce]: FaSalesforce,
[OAuthProvider.ServiceNow]: KeyIcon,
g-linville marked this conversation as resolved.
Show resolved Hide resolved
[OAuthProvider.Google]: FaGoogle,
[OAuthProvider.Microsoft365]: FaMicrosoft,
[OAuthProvider.Notion]: NotionLogoIcon,
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 @@ -8,6 +8,7 @@ import { GoogleOAuthApp } from "~/lib/model/oauthApps/providers/google";
import { Microsoft365OAuthApp } from "~/lib/model/oauthApps/providers/microsoft365";
import { NotionOAuthApp } from "~/lib/model/oauthApps/providers/notion";
import { SalesforceOAuthApp } from "~/lib/model/oauthApps/providers/salesforce";
import { ServiceNowOAuthApp } from "~/lib/model/oauthApps/providers/servicenow";
import { SlackOAuthApp } from "~/lib/model/oauthApps/providers/slack";
import { EntityMeta } from "~/lib/model/primitives";

Expand All @@ -18,6 +19,7 @@ export const OAuthAppSpecMap = {
[OAuthProvider.Microsoft365]: Microsoft365OAuthApp,
[OAuthProvider.Slack]: SlackOAuthApp,
[OAuthProvider.Salesforce]: SalesforceOAuthApp,
[OAuthProvider.ServiceNow]: ServiceNowOAuthApp,
[OAuthProvider.Notion]: NotionOAuthApp,
// Custom OAuth apps are intentionally omitted from the map.
// They are handled separately
Expand Down
1 change: 1 addition & 0 deletions ui/admin/app/lib/model/oauthApps/oauth-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const OAuthProvider = {
Microsoft365: "microsoft365",
Slack: "slack",
Salesforce: "salesforce",
ServiceNow: "servicenow",
Notion: "notion",
Custom: "custom",
} as const;
Expand Down
34 changes: 34 additions & 0 deletions ui/admin/app/lib/model/oauthApps/providers/servicenow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { z } from "zod";

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

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

const steps: OAuthFormStep<typeof schema.shape>[] = [
// TODO(njhale): Add instructions for how to set up the OAuth App in ServiceNow and get
// the required values below.
{ type: "input", input: "clientID", label: "Consumer Key" },
{
type: "input",
input: "clientSecret",
label: "Consumer Secret",
inputType: "password",
},
{ type: "input", input: "instanceURL", label: "Instance URL" },
];

export const ServiceNowOAuthApp = {
schema,
alias: "servicenow",
type: "servicenow",
displayName: "ServiceNow",
steps: steps,
noGatewayIntegration: true,
} satisfies OAuthAppSpec;