From d4701a71f803ba7ab925343fe324ccc2bbe20320 Mon Sep 17 00:00:00 2001 From: rathnapandi Date: Fri, 14 Apr 2023 11:08:29 -0700 Subject: [PATCH] support external oauth credenital rotation --- pkg/cmd/discovery/root.go | 6 +- pkg/subscription/provision.go | 122 ++++++++++++++++++++++++---------- pkg/webmethods/client.go | 27 ++++++++ pkg/webmethods/client_test.go | 19 ++++++ pkg/webmethods/types.go | 1 + 5 files changed, 138 insertions(+), 37 deletions(-) diff --git a/pkg/cmd/discovery/root.go b/pkg/cmd/discovery/root.go index 44b34f9..a76db29 100644 --- a/pkg/cmd/discovery/root.go +++ b/pkg/cmd/discovery/root.go @@ -101,8 +101,8 @@ func initConfig(centralConfig corecfg.CentralConfig) (interface{}, error) { SetName(subscription.ApplicationTypeField).SetRequired().SetLabel("Application Type"). IsString().SetEnumValues([]string{"Confidential", "Public"}).SetFirstEnumValue("Confidential") - audience := provisioning.NewSchemaPropertyBuilder(). - SetName(subscription.AudienceField).SetLabel("Audience").IsString().SetAsTextArea() + // audience := provisioning.NewSchemaPropertyBuilder(). + // SetName(subscription.AudienceField).SetLabel("Audience").IsString().SetAsTextArea() agent.NewAccessRequestBuilder().SetName(subscription.OAuth2AuthType).Register() @@ -111,7 +111,7 @@ func initConfig(centralConfig corecfg.CentralConfig) (interface{}, error) { coreagent.WithCRDOAuthSecret(), coreagent.WithCRDRequestSchemaProperty(oAuthServers), coreagent.WithCRDRequestSchemaProperty(oAuthType), - coreagent.WithCRDRequestSchemaProperty(audience), + // coreagent.WithCRDRequestSchemaProperty(audience), coreagent.WithCRDRequestSchemaProperty(oAuthRedirects), coreagent.WithCRDRequestSchemaProperty(corsProp)).SetName(subscription.OAuth2AuthType).IsRenewable().Register() diff --git a/pkg/subscription/provision.go b/pkg/subscription/provision.go index 324e93d..045e7da 100644 --- a/pkg/subscription/provision.go +++ b/pkg/subscription/provision.go @@ -52,15 +52,15 @@ func (p provisioner) AccessRequestDeprovision(req prov.AccessRequest) prov.Reque } // process access request delete - // webmethodsApplicationId := req.GetApplicationDetailsValue(common.AttrAppID) - // if webmethodsApplicationId == "" { - // return p.failed(rs, notFound(common.AttrAppID)) - // } + webmethodsApplicationId := req.GetApplicationDetailsValue(common.AttrAppID) + if webmethodsApplicationId == "" { + return p.failed(rs, notFound(common.AttrAppID)) + } - // err := p.client.UnsubscribeApplication(webmethodsApplicationId, apiID) - // if err != nil { - // return p.failed(rs, notFound("Error removing API from Webmethods Application")) - // } + err := p.client.UnsubscribeApplication(webmethodsApplicationId, apiID) + if err != nil { + return p.failed(rs, notFound("Error removing API from Webmethods Application")) + } p.log. WithField("api", apiID). @@ -118,7 +118,15 @@ func (p provisioner) ApplicationRequestDeprovision(req prov.ApplicationRequest) if webmethodsApplicationId == "" { return p.failed(rs, notFound(common.AttrAppID)) } - err := p.client.DeleteApplication(webmethodsApplicationId) + applicationResponse, err := p.client.GetApplication(webmethodsApplicationId) + if err != nil { + return p.failed(rs, notFound("Error calling webmethods")) + } + if len(applicationResponse.Applications) == 0 { + log.Warnf("Application with id %s is already deleted", webmethodsApplicationId) + return rs.Success() + } + err = p.client.DeleteApplication(webmethodsApplicationId) if err != nil { return p.failed(rs, notFound("Error Deleting Webmethods application")) } @@ -140,16 +148,28 @@ func (p provisioner) ApplicationRequestProvision(req prov.ApplicationRequest) pr return p.failed(rs, notFound("managed application name")) } - // process application create - var application webmethods.Application - application.Name = appName - application.Version = "1.0" - application.Description = "Amplify " + appName - createdApplication, err := p.client.CreateApplication(&application) + searchAppResponse, err := p.client.FindApplicationByName(appName) if err != nil { - return p.failed(rs, notFound("Error creating application")) + return p.failed(rs, notFound("Error contacting webmethods")) + } + var applicationId string + if len(searchAppResponse.SearchApplication) == 0 { + log.Infof("Creating new application with name %s", appName) + var application webmethods.Application + application.Name = appName + application.Version = "1.0" + application.Description = "Amplify " + appName + createdApplication, err := p.client.CreateApplication(&application) + if err != nil { + return p.failed(rs, notFound("Error creating application")) + } + applicationId = createdApplication.ApplicationID + } else { + log.Infof("Using the exsting application with Id %s", searchAppResponse.SearchApplication[0].ApplicationID) + applicationId = searchAppResponse.SearchApplication[0].ApplicationID } - rs.AddProperty(common.AttrAppID, createdApplication.Id) + // process application create + rs.AddProperty(common.AttrAppID, applicationId) p.log. WithField("appName", req.GetManagedApplicationName()). Info("created application") @@ -179,8 +199,20 @@ func (p provisioner) CredentialDeprovision(req prov.CredentialRequest) prov.Requ } case OAuth2AuthType: log.Info("Removing oauth credential") + applicationsResponse, err := p.client.GetApplication(webmethodsApplicationId) + if err != nil { + return p.failed(rs, notFound("Unable to get application from Webmethods")) + } + if len(applicationsResponse.Applications[0].AuthStrategyIds) == 0 { + log.Warnf("Oauth Credential already cleaned up for application %s", applicationsResponse.Applications[0].Name) + return rs.Success() + } + strategyId := applicationsResponse.Applications[0].AuthStrategyIds[0] + err = p.client.DeleteStrategy(strategyId) + if err != nil { + return p.failed(rs, notFound("Unable to delete Oauth2 strategy from Webmethods")) + } } - return rs.Success() } @@ -210,7 +242,19 @@ func (p provisioner) CredentialProvision(req prov.CredentialRequest) (prov.Reque switch req.GetCredentialType() { case prov.APIKeyCRD: - credential = prov.NewCredentialBuilder().SetAPIKey(applicationsResponse.Applications[0].AccessTokens.ApiAccessKeyCredentials.ApiAccessKey) + application := applicationsResponse.Applications[0] + if len(provData.cors) > 0 { + log.Infof("Update javascript origins for the application %s", application.Name) + // Updating java script origins + applicationsResponse.Applications[0].JsOrigins = append(application.JsOrigins, provData.cors...) + applicationUpdateResponse, err := p.client.UpdateApplication(&application) + if err != nil { + return p.failed(rs, notFound("Unable to to update Java Script Origins")), nil + } + credential = prov.NewCredentialBuilder().SetAPIKey(applicationUpdateResponse.AccessTokens.ApiAccessKeyCredentials.ApiAccessKey) + } else { + credential = prov.NewCredentialBuilder().SetAPIKey(application.AccessTokens.ApiAccessKeyCredentials.ApiAccessKey) + } case OAuth2AuthType: credential, err = createOrGetOauthCredential(applicationsResponse.Applications[0], provData, p) if err != nil { @@ -233,23 +277,27 @@ func (p provisioner) CredentialUpdate(req prov.CredentialRequest) (prov.RequestS if webmethodsApplicationId == "" { return p.failed(rs, notFound(common.AttrAppID)), nil } - err := p.client.RotateApplicationApikey(webmethodsApplicationId) - if err != nil { - return p.failed(rs, notFound("Unable to Rotate Webmethods Application APIkey")), nil - } - applicationsResponse, err := p.client.GetApplication(webmethodsApplicationId) - if err != nil { - return p.failed(rs, notFound("Unable to get application from Webmethods")), nil - } + var credential prov.Credential switch req.GetCredentialType() { case prov.APIKeyCRD: + err := p.client.RotateApplicationApikey(webmethodsApplicationId) + if err != nil { + return p.failed(rs, notFound("Unable to Rotate Webmethods Application APIkey")), nil + } + applicationsResponse, err := p.client.GetApplication(webmethodsApplicationId) + if err != nil { + return p.failed(rs, notFound("Unable to get application from Webmethods")), nil + } credential = prov.NewCredentialBuilder().SetAPIKey(applicationsResponse.Applications[0].AccessTokens.ApiAccessKeyCredentials.ApiAccessKey) case OAuth2AuthType: + applicationsResponse, err := p.client.GetApplication(webmethodsApplicationId) + if err != nil { + return p.failed(rs, notFound("Unable to get application from Webmethods")), nil + } strategyId := applicationsResponse.Applications[0].AuthStrategyIds[0] - log.Infof("Using existing Oauth Strategy named %s with id %s", applicationsResponse.Applications[0].Name, strategyId) - strategyResponse, err := p.client.GetStrategy(strategyId) + strategyResponse, err := p.client.RefereshOauth2Credential(strategyId) if err != nil { return p.failed(rs, notFound("Unable to get strategy from Webmethods")), nil } @@ -276,6 +324,7 @@ func getCredProvData(credData map[string]interface{}) credentialMetaData { cors: []string{"*"}, redirectURLs: []string{}, appType: "Confidential", + audience: "", } // get cors from credential request @@ -301,10 +350,10 @@ func getCredProvData(credData map[string]interface{}) credentialMetaData { credMetaData.appType = data.(string) } - // Audience type field - if data, ok := credData[AudienceField]; ok && data != nil { - credMetaData.audience = data.(string) - } + // // Audience type field + // if data, ok := credData[AudienceField]; ok && data != nil { + // credMetaData.audience = data.(string) + // } return credMetaData } @@ -334,16 +383,21 @@ func createOrGetOauthCredential(application webmethods.Application, provData cre ClientType: provData.appType, ExpirationInterval: "3600", RefreshCount: "100", + PkceType: "USE_GLOBAL_SETTING", } strategy := &webmethods.Strategy{ Name: application.Name, Description: application.Name, AuthServerAlias: provData.oauthServerName, Audience: provData.audience, - Type: "OAUTH2", + Type: "OAUTH2_LOCAL_RSA", DcrConfig: dcrconfig, } + if provData.oauthServerName == "local" { + strategy.Type = "OAUTH2" + } + strategyResponse, err = p.client.CreateOauth2Strategy(strategy) if err != nil { return nil, errors.New("Unable to get application from Webmethods") diff --git a/pkg/webmethods/client.go b/pkg/webmethods/client.go index a506829..7d9fa5f 100644 --- a/pkg/webmethods/client.go +++ b/pkg/webmethods/client.go @@ -39,6 +39,7 @@ type Client interface { RotateApplicationApikey(applicationId string) error CreateOauth2Strategy(strategy *Strategy) (*StrategyResponse, error) DeleteStrategy(strategyId string) error + RefereshOauth2Credential(strategyId string) (*StrategyResponse, error) GetStrategy(strategyId string) (*StrategyResponse, error) DeleteApplication(applicationId string) error OnConfigChange(webMethodConfig *config.WebMethodConfig) error @@ -466,6 +467,32 @@ func (c *WebMethodClient) DeleteStrategy(strategyId string) error { return nil } +func (c *WebMethodClient) RefereshOauth2Credential(strategyId string) (*StrategyResponse, error) { + strategyResponse := &StrategyResponse{} + + url := fmt.Sprintf("%s/rest/apigateway/strategies/%s/refreshCredentials", c.url, strategyId) + headers := map[string]string{ + "Authorization": c.createAuthToken(), + "Content-Type": "application/json", + } + request := coreapi.Request{ + Method: coreapi.PUT, + URL: url, + Headers: headers, + } + + response, err := c.httpClient.Send(request) + if err != nil { + return nil, err + } + + err = json.Unmarshal(response.Body, strategyResponse) + if err != nil { + return nil, err + } + return strategyResponse, nil +} + func (c *WebMethodClient) DeleteApplication(applicationId string) error { url := fmt.Sprintf("%s/rest/apigateway/applications/%s", c.url, applicationId) headers := map[string]string{ diff --git a/pkg/webmethods/client_test.go b/pkg/webmethods/client_test.go index 4a4c9bf..6b81c9a 100644 --- a/pkg/webmethods/client_test.go +++ b/pkg/webmethods/client_test.go @@ -5289,6 +5289,25 @@ func TestFindApplicationByName(t *testing.T) { } +func TestFindApplicationByNameNegative(t *testing.T) { + + response := `{ + "application": [] + }` + mc := &MockClient{} + webMethodsClient, _ := NewClient(cfg, mc) + mc.SendFunc = func(request coreapi.Request) (*coreapi.Response, error) { + return &coreapi.Response{ + Code: 200, + Body: []byte(response), + }, nil + } + applicationResponse, err := webMethodsClient.FindApplicationByName("oauthokta") + assert.Nil(t, err) + assert.Equal(t, len(applicationResponse.SearchApplication), 0) + +} + func TestCreateOauth2Strategy(t *testing.T) { request := `{ "name": "oauthdynamic2", diff --git a/pkg/webmethods/types.go b/pkg/webmethods/types.go index 58c21ed..e1cdad0 100644 --- a/pkg/webmethods/types.go +++ b/pkg/webmethods/types.go @@ -178,6 +178,7 @@ type DcrConfig struct { ClientType string `json:"clientType"` ExpirationInterval string `json:"expirationInterval"` RefreshCount string `json:"refreshCount"` + PkceType string `json:"pkceType"` } type StrategyResponse struct {