Skip to content

Commit

Permalink
feat: add servicenow oauth app integration
Browse files Browse the repository at this point in the history
Signed-off-by: Nick Hale <[email protected]>
  • Loading branch information
njhale committed Dec 30, 2024
1 parent 6ce2501 commit 2b6b097
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 3 deletions.
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,
[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;

0 comments on commit 2b6b097

Please sign in to comment.