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 27, 2024
1 parent 6ce2501 commit d36e8a4
Show file tree
Hide file tree
Showing 6 changed files with 87 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/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
35 changes: 35 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,35 @@
import { z } from "zod";

import {
OAuthAppSpec,
OAuthFormStep,
getOAuthLinks,

Check failure on line 6 in ui/admin/app/lib/model/oauthApps/providers/servicenow.ts

View workflow job for this annotation

GitHub Actions / lint

'getOAuthLinks' is defined but never used. Allowed unused vars must match /^_/u
} 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 d36e8a4

Please sign in to comment.