Skip to content

Commit 92c3d69

Browse files
committed
APIGOV-32516 - improve IDP lifecycle and credential handler
- Add GetAPIServerVersionURL() to CentralConfig for version-level API paths - Refactor to manage idp and idp metadata resources and use tokenEndpoint for lookups - Changes to manage provisioning/deprovisioning external mode credentials
1 parent fbcf9c6 commit 92c3d69

7 files changed

Lines changed: 89 additions & 103 deletions

File tree

pkg/agent/handler/credential.go

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ func (h *credentials) onDeleting(ctx context.Context, cred *management.Credentia
223223

224224
func (h *credentials) deprovisionPostProcess(status prov.RequestStatus, provCreds *provCreds, logger log.FieldLogger, ctx context.Context, cred *management.Credential) {
225225
if status.GetStatus() == prov.Success {
226-
if provCreds.IsIDPCredential() {
226+
if provCreds.IsIDPCredential() && !isExternalCredential(cred) {
227227
err := provCreds.idpProvisioner.UnregisterClient()
228228
if err != nil {
229229
logger.
@@ -339,19 +339,22 @@ func (h *credentials) provisionPostProcess(status prov.RequestStatus, credential
339339
var err error
340340
data := map[string]interface{}{}
341341
idpAgentDetails := make(map[string]string)
342+
isExternal := isExternalCredential(cred)
342343
if status.GetStatus() == prov.Success {
343344
credentialData := h.getProvisionedCredentialData(provCreds, credentialData)
344345
if credentialData != nil {
345-
sec := app.Spec.Security
346-
d := credentialData.GetData()
347-
if crd.Spec.Provision == nil {
348-
data = d
349-
} else if d != nil {
350-
data, err = h.encryptSchema(
351-
crd.Spec.Provision.Schema,
352-
d,
353-
sec.EncryptionKey, sec.EncryptionAlgorithm, sec.EncryptionHash,
354-
)
346+
if !isExternal {
347+
sec := app.Spec.Security
348+
d := credentialData.GetData()
349+
if crd.Spec.Provision == nil {
350+
data = d
351+
} else if d != nil {
352+
data, err = h.encryptSchema(
353+
crd.Spec.Provision.Schema,
354+
d,
355+
sec.EncryptionKey, sec.EncryptionAlgorithm, sec.EncryptionHash,
356+
)
357+
}
355358
}
356359
if provCreds.IsIDPCredential() {
357360
idpAgentDetails, err = provCreds.idpProvisioner.GetAgentDetails()
@@ -387,6 +390,14 @@ func (h *credentials) provisionPostProcess(status prov.RequestStatus, credential
387390

388391
h.processCredentialLevelSuccess(provCreds, cred)
389392

393+
if isExternal {
394+
cred.SubResources = map[string]interface{}{
395+
defs.XAgentDetails: util.GetAgentDetails(cred),
396+
"state": cred.State,
397+
}
398+
return
399+
}
400+
390401
cred.SubResources = map[string]interface{}{
391402
defs.XAgentDetails: util.GetAgentDetails(cred),
392403
"data": cred.Data,

pkg/agent/idplifecycle.go

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package agent
22

33
import (
4-
"fmt"
54
"sync"
65

76
management "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/management/v1alpha1"
@@ -43,7 +42,7 @@ func manageIDPResource(idpLogger log.FieldLogger, idp config.IDPConfig) string {
4342
return ""
4443
}
4544

46-
name := manageIDPResourceFromMetadata(idpLogger, idp, provider.GetMetadata())
45+
name := manageIDPResourceFromMetadata(idpLogger, idp, "", provider.GetMetadata())
4746
if name == "" {
4847
idpLogger.Warn("IdentityProvider resource could not be created or found; CRD will be registered without an IdentityProvider reference")
4948
return ""
@@ -65,7 +64,7 @@ func manageIDPResourceWithMetadata(idpLogger log.FieldLogger, idp config.IDPConf
6564

6665
idpLogger = idpLogger.WithField("environmentName", agent.cfg.GetEnvironmentName())
6766

68-
name := manageIDPResourceFromMetadata(idpLogger, idp, metadata)
67+
name := manageIDPResourceFromMetadata(idpLogger, idp, "", metadata)
6968
if name != "" {
7069
GetAuthProviderRegistry().SetIDPResourceName(idp.GetMetadataURL(), name)
7170
}
@@ -76,7 +75,7 @@ func manageIDPResourceWithMetadata(idpLogger log.FieldLogger, idp config.IDPConf
7675
// pre-resolved metadata. Public entry point for agents like v7 that supply metadata
7776
// directly without a discovery URL.
7877
// Returns the Engage IdentityProvider resource name, or empty string on failure.
79-
func ManageIDPResource(idpLogger log.FieldLogger, metadata *oauth.AuthorizationServerMetadata) string {
78+
func ManageIDPResource(idpLogger log.FieldLogger, idpName string, metadata *oauth.AuthorizationServerMetadata) string {
8079
if metadata == nil {
8180
idpLogger.Error("metadata is nil; cannot manage IdentityProvider resource")
8281
return ""
@@ -88,25 +87,8 @@ func ManageIDPResource(idpLogger log.FieldLogger, metadata *oauth.AuthorizationS
8887
return name
8988
}
9089

91-
// try finding IdentityProviderMetadata in Engage by token endpoint; scope name is the IdP resource name
92-
existing, err := agent.apicClient.GetAPIV1ResourceInstances(
93-
map[string]string{"query": fmt.Sprintf("spec.tokenEndpoint==\"%s\"", metadata.TokenEndpoint)},
94-
management.NewIdentityProviderMetadata("", "").GetKindLink(),
95-
)
96-
if err == nil && len(existing) > 0 {
97-
// IdentityProviderMetadata is scoped under IdentityProvider; scope name is the IdP resource name
98-
name := existing[0].Metadata.Scope.Name
99-
if name != "" {
100-
// store by token endpoint so subsequent calls skip the API lookup
101-
setIDPMetadataResourceName(metadata.TokenEndpoint, name)
102-
idpLogger.WithField("name", name).Info("found existing IdentityProvider resource in Engage via IdP metadata")
103-
return name
104-
}
105-
idpLogger.Warn("IdentityProviderMetadata found in Engage but scope name is empty; falling through to create")
106-
}
107-
108-
// not found in cache or Engage — create the IdP resource and cache it by token endpoint
109-
name := manageIDPResourceFromMetadata(idpLogger, nil, metadata)
90+
// not found in cache — create the IdP resource and cache it by token endpoint
91+
name := manageIDPResourceFromMetadata(idpLogger, nil, idpName, metadata)
11092
if name != "" {
11193
setIDPMetadataResourceName(metadata.TokenEndpoint, name)
11294
}
@@ -115,20 +97,21 @@ func ManageIDPResource(idpLogger log.FieldLogger, metadata *oauth.AuthorizationS
11597

11698
// manageIDPResourceFromMetadata is the shared internal entry point for both paths.
11799
// idpCfg is passed to the IDPResourceBuilder when a supplier is registered; may be nil for the v7 path.
118-
func manageIDPResourceFromMetadata(idpLogger log.FieldLogger, idpCfg config.IDPConfig, metadata *oauth.AuthorizationServerMetadata) string {
100+
func manageIDPResourceFromMetadata(idpLogger log.FieldLogger, idpCfg config.IDPConfig, idpName string, metadata *oauth.AuthorizationServerMetadata) string {
119101
if metadata == nil {
120102
idpLogger.Error("metadata is nil; cannot manage IdentityProvider resource")
121103
return ""
122104
}
123105

124106
idpLogger = idpLogger.WithField("issuer", metadata.Issuer)
125107

126-
idpName := metadata.Issuer
108+
idpType := oauth.TypeGeneric
127109
if idpCfg != nil {
128110
idpName = idpCfg.GetIDPName()
111+
idpType = idpCfg.GetIDPType()
129112
}
130113

131-
name, err := newLifecycle().CreateEngageResourcesFromMetadata(idpLogger, idpCfg, idpName, metadata, agent.cfg.GetEnvironmentURL(), getEnvCredentialPolicies(idpLogger))
114+
name, err := newLifecycle().CreateEngageResourcesFromMetadata(idpLogger, idpCfg, idpType, idpName, metadata, agent.cfg.GetAPIServerVersionURL(), getEnvCredentialPolicies(idpLogger))
132115
if err != nil {
133116
idpLogger.WithError(err).Warn("unable to create or find IdentityProvider resource in Engage")
134117
return ""

pkg/agent/idplifecycle_test.go

Lines changed: 23 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ func TestManageIDPResourceExistingFound(t *testing.T) {
153153
for name, tc := range tests {
154154
tc := tc
155155
t.Run(name, func(t *testing.T) {
156-
existingRI := idpInstanceRI(tc.existingName)
156+
existingRI := idpMetadataInstanceRI(tc.existingName)
157157
createCalled := false
158158
subResourceCalled := false
159159

@@ -677,7 +677,7 @@ func TestManageIDPResourceWithMetadataExistingFound(t *testing.T) {
677677
createCount := 0
678678
apicClient := &mock.Client{
679679
GetAPIV1ResourceInstancesMock: func(_ map[string]string, _ string) ([]*apiv1.ResourceInstance, error) {
680-
return []*apiv1.ResourceInstance{idpInstanceRI(tc.existingName)}, nil
680+
return []*apiv1.ResourceInstance{idpMetadataInstanceRI(tc.existingName)}, nil
681681
},
682682
CreateOrUpdateResourceMock: func(ri apiv1.Interface) (*apiv1.ResourceInstance, error) {
683683
createCount++
@@ -851,8 +851,8 @@ func TestManageIDPResource(t *testing.T) {
851851
wantCachedAfter bool
852852
}{
853853
"nil metadata returns empty without any API call": {
854-
metadata: nil,
855-
assertResult: func(t *testing.T, result string) { assert.Empty(t, result) },
854+
metadata: nil,
855+
assertResult: func(t *testing.T, result string) { assert.Empty(t, result) },
856856
wantQueryCount: 0,
857857
},
858858
"local cache hit skips Engage query and returns cached name": {
@@ -862,49 +862,27 @@ func TestManageIDPResource(t *testing.T) {
862862
wantQueryCount: 0,
863863
wantCachedAfter: true,
864864
},
865-
"Engage query finds IdentityProviderMetadata with non-empty scope name": {
866-
metadata: testMeta,
867-
tokenEndpointInstances: []*apiv1.ResourceInstance{idpMetadataInstanceRI("found-idp")},
868-
assertResult: func(t *testing.T, result string) { assert.Equal(t, "found-idp", result) },
869-
wantQueryCount: 1,
870-
wantCachedAfter: true,
871-
},
872-
"Engage query finds result with empty scope name falls through to create": {
873-
metadata: testMeta,
874-
tokenEndpointInstances: []*apiv1.ResourceInstance{idpMetadataInstanceRI("")},
875-
metadataURLInstances: []*apiv1.ResourceInstance{},
876-
assertResult: func(t *testing.T, result string) { assert.NotEmpty(t, result) },
877-
wantQueryCount: 2,
878-
wantCreateCalled: true,
879-
wantCachedAfter: true,
880-
},
881-
"Engage query error falls through to create": {
882-
metadata: testMeta,
883-
tokenEndpointErr: errors.New("query error"),
884-
metadataURLInstances: []*apiv1.ResourceInstance{},
885-
assertResult: func(t *testing.T, result string) { assert.NotEmpty(t, result) },
886-
wantQueryCount: 2,
887-
wantCreateCalled: true,
888-
wantCachedAfter: true,
865+
"creates resource when not in cache": {
866+
metadata: testMeta,
867+
assertResult: func(t *testing.T, result string) { assert.NotEmpty(t, result) },
868+
wantQueryCount: 1,
869+
wantCreateCalled: true,
870+
wantCachedAfter: true,
889871
},
890-
"not in cache or Engage creates resource and caches by token endpoint": {
891-
metadata: testMeta,
892-
tokenEndpointInstances: []*apiv1.ResourceInstance{},
893-
metadataURLInstances: []*apiv1.ResourceInstance{},
894-
assertResult: func(t *testing.T, result string) { assert.NotEmpty(t, result) },
895-
wantQueryCount: 2,
896-
wantCreateCalled: true,
897-
wantCachedAfter: true,
872+
"creates and caches resource when not in cache": {
873+
metadata: testMeta,
874+
assertResult: func(t *testing.T, result string) { assert.NotEmpty(t, result) },
875+
wantQueryCount: 1,
876+
wantCreateCalled: true,
877+
wantCachedAfter: true,
898878
},
899879
"create failure returns empty name and nothing cached": {
900-
metadata: testMeta,
901-
tokenEndpointInstances: []*apiv1.ResourceInstance{},
902-
metadataURLInstances: []*apiv1.ResourceInstance{},
903-
createErr: errors.New("create failed"),
904-
assertResult: func(t *testing.T, result string) { assert.Empty(t, result) },
905-
wantQueryCount: 2,
906-
wantCreateCalled: true,
907-
wantCachedAfter: false,
880+
metadata: testMeta,
881+
createErr: errors.New("create failed"),
882+
assertResult: func(t *testing.T, result string) { assert.Empty(t, result) },
883+
wantQueryCount: 1,
884+
wantCreateCalled: true,
885+
wantCachedAfter: false,
908886
},
909887
}
910888

@@ -949,7 +927,7 @@ func TestManageIDPResource(t *testing.T) {
949927
setIDPMetadataResourceName(testMeta.TokenEndpoint, tc.preloadCacheName)
950928
}
951929

952-
result := ManageIDPResource(logger, tc.metadata)
930+
result := ManageIDPResource(logger, testIDPName, tc.metadata)
953931

954932
tc.assertResult(t, result)
955933
assert.Equal(t, tc.wantQueryCount, queryCount)

pkg/apic/resourcePagination.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func (c *ServiceClient) GetAPIV1ResourceInstancesWithPageSize(queryParams map[st
8484

8585
log := c.logger.WithField("endpoint", url)
8686
log.Trace("retrieving all resources from endpoint")
87-
if !strings.HasPrefix(url, c.cfg.GetAPIServerURL()) {
87+
if !strings.HasPrefix(url, c.cfg.GetAPIServerURL()) && !strings.HasPrefix(url, c.cfg.GetAPIServerVersionURL()) {
8888
url = c.createAPIServerURL(url)
8989
}
9090

pkg/authz/oauth/idplifecycle.go

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ import (
1010
"github.com/Axway/agent-sdk/pkg/util/log"
1111
)
1212

13+
const (
14+
defaultIdpClientTimeoutSeconds = 60
15+
)
16+
1317
// IDPClient is the subset of apic.Client used by the IdP lifecycle manager,
1418
// defined here to avoid a circular import with pkg/apic.
1519
type IDPClient interface {
@@ -32,7 +36,7 @@ type IDPEngageLifecycle interface {
3236
// resources in Engage using pre-resolved metadata — no Provider or outbound HTTP fetch required.
3337
// idpCfg is optional (may be nil for the v7 path); when set it is passed to the IDPResourceBuilder.
3438
// Returns the Engage IdentityProvider resource name.
35-
CreateEngageResourcesFromMetadata(idpLogger log.FieldLogger, idpCfg corecfg.IDPConfig, idpName string, metadata *AuthorizationServerMetadata, envURL string, envPolicies management.EnvironmentPoliciesCredentials) (string, error)
39+
CreateEngageResourcesFromMetadata(idpLogger log.FieldLogger, idpCfg corecfg.IDPConfig, idpType, idpName string, metadata *AuthorizationServerMetadata, baseUrl string, envPolicies management.EnvironmentPoliciesCredentials) (string, error)
3640
}
3741

3842
// LifecycleOption configures an idpEngageLifecycle.
@@ -57,28 +61,25 @@ func NewIDPEngageLifecycle(client IDPClient, opts ...LifecycleOption) IDPEngageL
5761
return l
5862
}
5963

60-
func (l *idpEngageLifecycle) CreateEngageResourcesFromMetadata(idpLogger log.FieldLogger, idpCfg corecfg.IDPConfig, idpName string, metadata *AuthorizationServerMetadata, envURL string, envPolicies management.EnvironmentPoliciesCredentials) (string, error) {
61-
metadataURL := metadata.Issuer
62-
if idpCfg != nil {
63-
metadataURL = idpCfg.GetMetadataURL()
64-
}
64+
func (l *idpEngageLifecycle) CreateEngageResourcesFromMetadata(idpLogger log.FieldLogger, idpCfg corecfg.IDPConfig, idpType, idpName string, metadata *AuthorizationServerMetadata, baseUrl string, envPolicies management.EnvironmentPoliciesCredentials) (string, error) {
65+
tokenEndpoint := metadata.TokenEndpoint
6566

6667
idpLogger.Debug("querying Engage for existing IdentityProvider resource")
6768
existing, err := l.client.GetAPIV1ResourceInstances(
68-
map[string]string{"query": fmt.Sprintf("spec.metadataUrl==\"%s\"", metadataURL)},
69-
envURL+"/"+management.IdentityProviderResourceName,
69+
map[string]string{"query": fmt.Sprintf("spec.tokenEndpoint==\"%s\"", tokenEndpoint)},
70+
baseUrl+"/"+management.NewIdentityProviderMetadata("", "").PluralName(),
7071
)
7172
if err != nil {
7273
return "", err
7374
}
7475

7576
if len(existing) > 0 {
76-
name := existing[0].Name
77+
name := existing[0].GetMetadata().Scope.Name
7778
idpLogger.WithField("name", name).Info("reusing existing IdentityProvider resource")
7879
return name, nil
7980
}
8081

81-
idpResource, err := l.buildIdentityProviderFromMetadata(idpLogger, idpCfg, idpName, metadataURL)
82+
idpResource, err := l.buildIdentityProviderFromMetadata(idpLogger, idpCfg, idpType, idpName)
8283
if err != nil {
8384
return "", err
8485
}
@@ -106,7 +107,7 @@ func (l *idpEngageLifecycle) CreateEngageResourcesFromMetadata(idpLogger log.Fie
106107
return createdName, nil
107108
}
108109

109-
func (l *idpEngageLifecycle) buildIdentityProviderFromMetadata(idpLogger log.FieldLogger, idpCfg corecfg.IDPConfig, idpName, metadataURL string) (*management.IdentityProvider, error) {
110+
func (l *idpEngageLifecycle) buildIdentityProviderFromMetadata(idpLogger log.FieldLogger, idpCfg corecfg.IDPConfig, idpType, idpName string) (*management.IdentityProvider, error) {
110111
if l.builder != nil && idpCfg != nil {
111112
idpLogger.Debug("building IdentityProvider resource via supplier")
112113
res, err := l.builder.GetIdentityProvider(idpCfg)
@@ -120,7 +121,8 @@ func (l *idpEngageLifecycle) buildIdentityProviderFromMetadata(idpLogger log.Fie
120121
name := util.NormalizeNameForCentral(idpName)
121122
res := management.NewIdentityProvider(name)
122123
res.Spec = management.IdentityProviderSpec{
123-
MetadataUrl: metadataURL,
124+
ProviderType: idpType,
125+
ClientTimeout: defaultIdpClientTimeoutSeconds,
124126
}
125127
return res, nil
126128
}

0 commit comments

Comments
 (0)