From c591cdd1d64bbabcbe1268efdf2c0c027f036568 Mon Sep 17 00:00:00 2001 From: guyp-descope Date: Wed, 20 Dec 2023 14:33:02 +0200 Subject: [PATCH 1/9] Add new SSO SAML/OIDC logic --- README.md | 71 ++++- descope/api/client.go | 44 ++- descope/internal/auth/auth.go | 13 + descope/internal/auth/sso.go | 61 ++++ descope/internal/auth/sso_test.go | 71 +++++ descope/internal/mgmt/sso.go | 121 ++++++++ descope/internal/mgmt/sso_test.go | 289 ++++++++++++++++++ descope/internal/mgmt/tenant.go | 1 + descope/sdk/auth.go | 14 + descope/sdk/mgmt.go | 45 ++- .../tests/mocks/auth/authenticationmock.go | 31 ++ descope/tests/mocks/mgmt/managementmock.go | 55 +++- descope/types.go | 87 +++++- 13 files changed, 878 insertions(+), 25 deletions(-) create mode 100644 descope/internal/auth/sso.go create mode 100644 descope/internal/auth/sso_test.go diff --git a/README.md b/README.md index e816757c..56da4dc4 100644 --- a/README.md +++ b/README.md @@ -245,14 +245,28 @@ if err != nil { The session and refresh JWTs should be returned to the caller, and passed with every request in the session. Read more on [session validation](#session-validation) -### SSO/SAML +### SSO SAML/OIDC -Users can authenticate to a specific tenant using SAML or Single Sign On. Configure your SSO/SAML settings on the [Descope console](https://app.descope.com/settings/authentication/sso). To start a flow call: +Users can authenticate to a specific tenant using SAML or OIDC. Configure your SSO SAML/OIDC settings on the [Descope console](https://app.descope.com/settings/authentication/sso). To start a flow call: ```go // Choose which tenant to log into // If configured globally, the return URL is optional. If provided however, it will be used // instead of any global configuration. +// Redirect the user to the returned URL to start the SSO SAML/OIDC redirect chain +url, err := descopeClient.Auth.SSO().Start("my-tenant-ID", "https://my-app.com/handle-saml", nil, nil, w) +if err != nil { + // handle error +} +``` + + +```go +// Deprecated +// +// Choose which tenant to log into +// If configured globally, the return URL is optional. If provided however, it will be used +// instead of any global configuration. // Redirect the user to the returned URL to start the SSO/SAML redirect chain url, err := descopeClient.Auth.SAML().Start("my-tenant-ID", "https://my-app.com/handle-saml", nil, nil, w) if err != nil { @@ -265,6 +279,17 @@ The user will authenticate with the authentication provider configured for that ```go // The optional `w http.ResponseWriter` adds the session and refresh cookies to the response automatically. // Otherwise they're available via authInfo +authInfo, err := descopeClient.Auth.SSO().ExchangeToken(code, w) +if err != nil { + // handle error +} +``` + +```go +// Deprecated +// +// The optional `w http.ResponseWriter` adds the session and refresh cookies to the response automatically. +// Otherwise they're available via authInfo authInfo, err := descopeClient.Auth.SAML().ExchangeToken(code, w) if err != nil { // handle error @@ -742,22 +767,56 @@ err := descopeClient.Management.AccessKey().Delete("access-key-id") ### Manage SSO Setting -You can manage SSO settings and map SSO group roles and user attributes. +You can manage SSO SAML or OIDC settings for a specific tenant. ```go -// You can get SSO settings for a specific tenant ID + +// Load all tenant SSO settings +ssoSettings, err := cc.HC.DescopeClient().Management.SSO().LoadSettings("tenant-id") +// Deprecated ssoSettings, err := descopeClient.Management.SSO().GetSettings("tenant-id") -// You can configure SSO settings manually by setting the required fields directly +// Configure tenant SSO by OIDC settings + +oidcSettings := &descope.SSOOIDCSettings{..} +err = cc.HC.DescopeClient().Management.SSO().ConfigureOIDCSettings("tenant-id", oidcSettings, "https://redirectlocation.com", "") +// OR +// Load all tenant SSO settings and use them for configure OIDC settings +ssoSettings, err := cc.HC.DescopeClient().Management.SSO().LoadSettings("tenant-id") +ssoSettings.Oidc.Name = "my prOvider" +ssoSettings.Oidc.AuthURL = authorizeEndpoint +... +ssoSettings.Oidc.Scope = []string{"openid", "profile", "email"} +err = cc.HC.DescopeClient().Management.SSO().ConfigureOIDCSettings("tenant-id", ssoSettings.Oidc, "https://redirectlocation.com", "") + +// Configure tenant SSO by SAML settings tenantID := "tenant-id" // Which tenant this configuration is for idpURL := "https://idp.com" entityID := "my-idp-entity-id" idpCert := "" redirectURL := "https://my-app.com/handle-saml" // Global redirect URL for SSO/SAML domain := "domain.com" // Users logging in from this domain will be logged in to this tenant +samlSettings := &descope.SSOSAMLSettings{ + IdpURL: idpURL, + IdpEntityID: entityID, + IdpCert: idpCert, + AttributeMapping: &descope.AttributeMapping{Email: "myEmail", ..}, + RoleMappings: []*RoleMapping{{..}}, +} +err = cc.HC.DescopeClient().Management.SSO().ConfigureSAMLSettings(tenantID, samlSettings, redirectURL, domain) + +// Deprecated err := descopeClient.Management.SSO().ConfigureSettings(tenantID, idpURL, entityID, idpCert, redirectURL, domain) -// Alternatively, configure using an SSO metadata URL +// Alternatively, configure using an SSO SAML metadata URL +samlSettings := &descope.SSOSAMLSettingsByMetadata{ + IdpMetadataURL: "https://idp.com/my-idp-metadata", + AttributeMapping: &descope.AttributeMapping{Email: "myEmail", ..}, + RoleMappings: []*RoleMapping{{..}}, +} +err = cc.HC.DescopeClient().Management.SSO().ConfigureSAMLSettingsByMetadata(tenantID, samlSettings, redirectURL, domain) + +// Deprecated err := descopeClient.Management.SSO().ConfigureMetadata(tenantID, "https://idp.com/my-idp-metadata", redirectURL, domain) // Map IDP groups to Descope roles, or map user attributes. diff --git a/descope/api/client.go b/descope/api/client.go index e0640572..113acd0c 100644 --- a/descope/api/client.go +++ b/descope/api/client.go @@ -58,6 +58,8 @@ var ( exchangeTokenOAuth: "auth/oauth/exchange", samlStart: "auth/saml/authorize", exchangeTokenSAML: "auth/saml/exchange", + ssoStart: "auth/sso/authorize", + exchangeTokenSSO: "auth/sso/exchange", webauthnSignUpStart: "auth/webauthn/signup/start", webauthnSignUpFinish: "auth/webauthn/signup/finish", webauthnSignInStart: "auth/webauthn/signin/start", @@ -113,6 +115,10 @@ var ( accessKeyActivate: "mgmt/accesskey/activate", accessKeyDelete: "mgmt/accesskey/delete", ssoSettings: "mgmt/sso/settings", + ssoLoadSettings: "mgmt/sso/loadsettings", + ssoSAMLSettings: "mgmt/sso/samlsettings", + ssoSAMLSettingsByMetadata: "mgmt/sso/samlsettingsbymetadata", + ssoOIDCSettings: "mgmt/sso/oidcsettings", ssoMetadata: "mgmt/sso/metadata", ssoMapping: "mgmt/sso/mapping", updateJWT: "mgmt/jwt/update", @@ -202,7 +208,9 @@ type authEndpoints struct { oauthStart string exchangeTokenOAuth string samlStart string + ssoStart string exchangeTokenSAML string + exchangeTokenSSO string webauthnSignUpStart string webauthnSignUpFinish string webauthnSignInStart string @@ -262,10 +270,16 @@ type mgmtEndpoints struct { accessKeyActivate string accessKeyDelete string + // // Deprecated ssoSettings string ssoMetadata string ssoMapping string - updateJWT string + //////////////////////////////// + ssoLoadSettings string + ssoSAMLSettings string + ssoSAMLSettingsByMetadata string + ssoOIDCSettings string + updateJWT string permissionCreate string permissionUpdate string @@ -387,12 +401,23 @@ func (e *endpoints) OAuthStart() string { func (e *endpoints) ExchangeTokenOAuth() string { return path.Join(e.version, e.auth.exchangeTokenOAuth) } + +/* Deprecated */ func (e *endpoints) SAMLStart() string { return path.Join(e.version, e.auth.samlStart) } + +/* Deprecated */ func (e *endpoints) ExchangeTokenSAML() string { return path.Join(e.version, e.auth.exchangeTokenSAML) } + +func (e *endpoints) SSOStart() string { + return path.Join(e.version, e.auth.ssoStart) +} +func (e *endpoints) ExchangeTokenSSO() string { + return path.Join(e.version, e.auth.exchangeTokenSSO) +} func (e *endpoints) WebAuthnSignUpStart() string { return path.Join(e.version, e.auth.webauthnSignUpStart) } @@ -609,6 +634,21 @@ func (e *endpoints) ManagementAccessKeyDelete() string { return path.Join(e.version, e.mgmt.accessKeyDelete) } +func (e *endpoints) ManagementSSOLoadSettings() string { + return path.Join(e.version, e.mgmt.ssoLoadSettings) +} + +func (e *endpoints) ManagementSSOSAMLSettings() string { + return path.Join(e.version, e.mgmt.ssoSAMLSettings) +} +func (e *endpoints) ManagementSSOSAMLSettingsByMetadata() string { + return path.Join(e.version, e.mgmt.ssoSAMLSettingsByMetadata) +} +func (e *endpoints) ManagementSSOOIDCSettings() string { + return path.Join(e.version, e.mgmt.ssoOIDCSettings) +} + +// // Deprecated func (e *endpoints) ManagementSSOSettings() string { return path.Join(e.version, e.mgmt.ssoSettings) } @@ -621,6 +661,8 @@ func (e *endpoints) ManagementSSOMapping() string { return path.Join(e.version, e.mgmt.ssoMapping) } +//////////////////////////////////// + func (e *endpoints) ManagementUpdateJWT() string { return path.Join(e.version, e.mgmt.updateJWT) } diff --git a/descope/internal/auth/auth.go b/descope/internal/auth/auth.go index b5f84cff..8d8d08a7 100644 --- a/descope/internal/auth/auth.go +++ b/descope/internal/auth/auth.go @@ -43,6 +43,7 @@ type authenticationService struct { webAuthn sdk.WebAuthn oauth sdk.OAuth saml sdk.SAML + sso sdk.SSOSP } func NewAuth(conf AuthParams, c *api.Client) (*authenticationService, error) { @@ -54,6 +55,7 @@ func NewAuth(conf AuthParams, c *api.Client) (*authenticationService, error) { authenticationService.enchantedLink = &enchantedLink{authenticationsBase: base} authenticationService.oauth = &oauth{authenticationsBase: base} authenticationService.saml = &saml{authenticationsBase: base} + authenticationService.sso = &sso{authenticationsBase: base} authenticationService.webAuthn = &webAuthn{authenticationsBase: base} authenticationService.totp = &totp{authenticationsBase: base} authenticationService.password = &password{authenticationsBase: base} @@ -88,6 +90,10 @@ func (auth *authenticationService) SAML() sdk.SAML { return auth.saml } +func (auth *authenticationService) SSO() sdk.SSOSP { + return auth.sso +} + func (auth *authenticationService) WebAuthn() sdk.WebAuthn { return auth.webAuthn } @@ -832,6 +838,13 @@ func composeSAMLExchangeTokenURL() string { return api.Routes.ExchangeTokenSAML() } +func composeSSOStartURL() string { + return api.Routes.SSOStart() +} +func composeSSOExchangeTokenURL() string { + return api.Routes.ExchangeTokenSSO() +} + func composeUpdateUserEmailOTP() string { return api.Routes.UpdateUserEmailOTP() } diff --git a/descope/internal/auth/sso.go b/descope/internal/auth/sso.go new file mode 100644 index 00000000..e3a66433 --- /dev/null +++ b/descope/internal/auth/sso.go @@ -0,0 +1,61 @@ +package auth + +import ( + "net/http" + + "github.com/descope/go-sdk/descope" + "github.com/descope/go-sdk/descope/api" + "github.com/descope/go-sdk/descope/internal/utils" + "github.com/descope/go-sdk/descope/logger" +) + +type sso struct { + authenticationsBase +} + +type ssoStartResponse struct { + URL string `json:"url"` +} + +func (auth *sso) Start(tenant string, redirectURL string, prompt string, r *http.Request, loginOptions *descope.LoginOptions, w http.ResponseWriter) (url string, err error) { + if tenant == "" { + return "", utils.NewInvalidArgumentError("tenant") + } + m := map[string]string{ + "tenant": string(tenant), + } + if len(redirectURL) > 0 { + m["redirectURL"] = redirectURL + } + if len(prompt) > 0 { + m["prompt"] = prompt + } + var pswd string + if loginOptions.IsJWTRequired() { + pswd, err = getValidRefreshToken(r) + if err != nil { + return "", descope.ErrInvalidStepUpJWT + } + } + httpResponse, err := auth.client.DoPostRequest(composeSSOStartURL(), loginOptions, &api.HTTPRequest{QueryParams: m}, pswd) + if err != nil { + return + } + + if httpResponse.Res != nil { + res := &ssoStartResponse{} + err = utils.Unmarshal([]byte(httpResponse.BodyStr), res) + if err != nil { + logger.LogError("Failed to parse sso location from response for [%s]", err, tenant) + return "", err + } + url = res.URL + redirectToURL(url, w) + } + + return +} + +func (auth *sso) ExchangeToken(code string, w http.ResponseWriter) (*descope.AuthenticationInfo, error) { + return auth.exchangeToken(code, composeSSOExchangeTokenURL(), w) +} diff --git a/descope/internal/auth/sso_test.go b/descope/internal/auth/sso_test.go new file mode 100644 index 00000000..48e963dc --- /dev/null +++ b/descope/internal/auth/sso_test.go @@ -0,0 +1,71 @@ +package auth + +import ( + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + + "github.com/descope/go-sdk/descope" + "github.com/descope/go-sdk/descope/api" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSSOStart(t *testing.T) { + uri := "http://test.me" + tenant := "tenantID" + prompt := "none" + landingURL := "https://test.com" + a, err := newTestAuth(nil, DoRedirect(uri, func(r *http.Request) { + assert.EqualValues(t, fmt.Sprintf("%s?prompt=%s&redirectURL=%s&tenant=%s", composeSSOStartURL(), prompt, url.QueryEscape(landingURL), tenant), r.URL.RequestURI()) + assert.Nil(t, r.Body) + })) + require.NoError(t, err) + w := httptest.NewRecorder() + urlStr, err := a.SSO().Start(tenant, landingURL, prompt, nil, nil, w) + require.NoError(t, err) + assert.EqualValues(t, uri, urlStr) + assert.EqualValues(t, urlStr, w.Result().Header.Get(descope.RedirectLocationCookieName)) + assert.EqualValues(t, http.StatusTemporaryRedirect, w.Result().StatusCode) +} + +func TestSSOStartStepup(t *testing.T) { + uri := "http://test.me" + tenant := "tenantID" + prompt := "none" + landingURL := "https://test.com" + a, err := newTestAuth(nil, DoRedirect(uri, func(r *http.Request) { + assert.EqualValues(t, fmt.Sprintf("%s?prompt=%s&redirectURL=%s&tenant=%s", composeSSOStartURL(), prompt, url.QueryEscape(landingURL), tenant), r.URL.RequestURI()) + body, err := readBodyMap(r) + require.NoError(t, err) + assert.EqualValues(t, map[string]interface{}{"stepup": true, "customClaims": map[string]interface{}{"k1": "v1"}}, body) + reqToken := r.Header.Get(api.AuthorizationHeaderName) + splitToken := strings.Split(reqToken, api.BearerAuthorizationPrefix) + require.Len(t, splitToken, 2) + bearer := splitToken[1] + bearers := strings.Split(bearer, ":") + require.Len(t, bearers, 2) + assert.EqualValues(t, "test", bearers[1]) + })) + require.NoError(t, err) + w := httptest.NewRecorder() + urlStr, err := a.SSO().Start(tenant, landingURL, prompt, &http.Request{Header: http.Header{"Cookie": []string{"DSR=test"}}}, &descope.LoginOptions{Stepup: true, CustomClaims: map[string]interface{}{"k1": "v1"}}, w) + require.NoError(t, err) + assert.EqualValues(t, uri, urlStr) + assert.EqualValues(t, urlStr, w.Result().Header.Get(descope.RedirectLocationCookieName)) + assert.EqualValues(t, http.StatusTemporaryRedirect, w.Result().StatusCode) +} + +func TestSSOStartInvalidForwardResponse(t *testing.T) { + a, err := newTestAuth(nil, nil) + require.NoError(t, err) + w := httptest.NewRecorder() + _, err = a.SAML().Start("", "", nil, nil, w) + require.Error(t, err) + + _, err = a.SSO().Start("test", "", "", nil, &descope.LoginOptions{Stepup: true}, w) + assert.ErrorIs(t, err, descope.ErrInvalidStepUpJWT) +} diff --git a/descope/internal/mgmt/sso.go b/descope/internal/mgmt/sso.go index 0e105116..2cb6235d 100644 --- a/descope/internal/mgmt/sso.go +++ b/descope/internal/mgmt/sso.go @@ -10,6 +10,118 @@ type sso struct { managementBase } +func (s *sso) LoadSettings(tenantID string) (*descope.SSOTenantSettingsResponse, error) { + if tenantID == "" { + return nil, utils.NewInvalidArgumentError("tenantID") + } + + req := &api.HTTPRequest{ + QueryParams: map[string]string{"tenantId": tenantID}, + } + res, err := s.client.DoGetRequest(api.Routes.ManagementSSOLoadSettings(), req, s.conf.ManagementKey) + if err != nil { + return nil, err + } + return unmarshalSSOTenantSettingsResponse(res) +} + +func (s *sso) ConfigureSAMLSettings(tenantID string, settings *descope.SSOSAMLSettings, redirectURL string, domain string) error { + if tenantID == "" { + return utils.NewInvalidArgumentError("tenantID") + } + + if settings == nil { + return utils.NewInvalidArgumentError("settings") + } + + if settings.IdpURL == "" { + return utils.NewInvalidArgumentError("idpURL") + } + if settings.IdpCert == "" { + return utils.NewInvalidArgumentError("idpCert") + } + if settings.IdpEntityID == "" { + return utils.NewInvalidArgumentError("idpEntityID") + } + + mappings := []map[string]any{} + for i := range settings.RoleMappings { + mappings = append(mappings, map[string]any{ + "groups": settings.RoleMappings[i].Groups, + "roleName": settings.RoleMappings[i].Role, + }) + } + + req := map[string]any{ + "tenantId": tenantID, + "settings": map[string]any{ + "idpURL": settings.IdpURL, + "entityId": settings.IdpEntityID, + "idpCert": settings.IdpCert, + "roleMappings": mappings, + "attributeMapping": settings.AttributeMapping, + }, + "redirectURL": redirectURL, + "domain": domain, + } + _, err := s.client.DoPostRequest(api.Routes.ManagementSSOSAMLSettings(), req, nil, s.conf.ManagementKey) + return err +} +func (s *sso) ConfigureSAMLSettingsByMetadata(tenantID string, settings *descope.SSOSAMLSettingsByMetadata, redirectURL string, domain string) error { + if tenantID == "" { + return utils.NewInvalidArgumentError("tenantID") + } + + if settings == nil { + return utils.NewInvalidArgumentError("settings") + } + + if settings.IdpMetadataURL == "" { + return utils.NewInvalidArgumentError("idpMetadataURL") + } + + mappings := []map[string]any{} + for i := range settings.RoleMappings { + mappings = append(mappings, map[string]any{ + "groups": settings.RoleMappings[i].Groups, + "roleName": settings.RoleMappings[i].Role, + }) + } + + req := map[string]any{ + "tenantId": tenantID, + "settings": map[string]any{ + "idpMetadataURL": settings.IdpMetadataURL, + "roleMappings": mappings, + "attributeMapping": settings.AttributeMapping, + }, + "redirectURL": redirectURL, + "domain": domain, + } + _, err := s.client.DoPostRequest(api.Routes.ManagementSSOSAMLSettingsByMetadata(), req, nil, s.conf.ManagementKey) + return err +} + +func (s *sso) ConfigureOIDCSettings(tenantID string, settings *descope.SSOOIDCSettings, redirectURL string, domain string) error { + if tenantID == "" { + return utils.NewInvalidArgumentError("tenantID") + } + + if settings == nil { + return utils.NewInvalidArgumentError("settings") + } + + req := map[string]any{ + "tenantId": tenantID, + "settings": settings, + "redirectURL": redirectURL, + "domain": domain, + } + + _, err := s.client.DoPostRequest(api.Routes.ManagementSSOOIDCSettings(), req, nil, s.conf.ManagementKey) + return err +} + func (s *sso) GetSettings(tenantID string) (*descope.SSOSettingsResponse, error) { if tenantID == "" { return nil, utils.NewInvalidArgumentError("tenantID") @@ -108,3 +220,12 @@ func unmarshalSSOSettingsResponse(res *api.HTTPResponse) (*descope.SSOSettingsRe } return ssoSettingsRes, err } + +func unmarshalSSOTenantSettingsResponse(res *api.HTTPResponse) (*descope.SSOTenantSettingsResponse, error) { + var ssoSettingsRes *descope.SSOTenantSettingsResponse + err := utils.Unmarshal([]byte(res.BodyStr), &ssoSettingsRes) + if err != nil { + return nil, err + } + return ssoSettingsRes, err +} diff --git a/descope/internal/mgmt/sso_test.go b/descope/internal/mgmt/sso_test.go index 4c29550b..6dd4050f 100644 --- a/descope/internal/mgmt/sso_test.go +++ b/descope/internal/mgmt/sso_test.go @@ -191,3 +191,292 @@ func TestSSOConfigureMappingError(t *testing.T) { err := mgmt.SSO().ConfigureMapping("", nil, nil) require.Error(t, err) } + +func TestLoadSettingsSuccess(t *testing.T) { + tenantID := "abc" + response := map[string]any{ + "tenant": map[string]any{ + "id": tenantID, + "name": "T1", + "authType": "saml", + "domain": "lulu", + }, + "saml": map[string]any{ + "tenantID": tenantID, + "idpEntityID": "idpEntityID", + "idpSSOURL": "idpSSOURL", + "idpCertificate": "idpCertificate", + "idpMetadataURL": "idpMetadataURL", + "spEntityId": "spEntityId", + "spACSUrl": "spACSUrl", + "spCertificate": "spCertificate", + "attributeMapping": map[string]string{ + "name": "name", + "email": "email", + "username": "username", + "phoneNumber": "phoneNumber", + "group": "group", + }, + "groupsMapping": []map[string]any{ + { + "role": map[string]string{ + "id": "role.id", + "name": "role.name", + }, + "groups": []string{"group1"}, + }, + }, + "redirectURL": "redirectURL", + "domain": "lulu", + }, + "oidc": map[string]any{ + "name": "myName", + "clientId": "abcdef", + "authUrl": "http://dummy.com", + "tokenUrl": "http://dummy.com", + "userDataUrl": "http://dummy.com", + "attributeMapping": map[string]any{ + "givenName": "myGivenName", + }, + }, + } + mgmt := newTestMgmt(nil, helpers.DoOkWithBody(func(r *http.Request) { + require.Equal(t, r.Header.Get("Authorization"), "Bearer a:key") + params := helpers.ReadParams(r) + require.Equal(t, tenantID, params["tenantId"]) + }, response)) + res, err := mgmt.SSO().LoadSettings(tenantID) + require.NoError(t, err) + require.NotNil(t, res.Tenant) + assert.EqualValues(t, tenantID, res.Tenant.ID) + assert.EqualValues(t, "lulu", res.Tenant.Domain) + + require.NotNil(t, res.Saml) + assert.EqualValues(t, "idpEntityID", res.Saml.IdpEntityID) + assert.EqualValues(t, "idpSSOURL", res.Saml.IdpSSOURL) + assert.EqualValues(t, "idpCertificate", res.Saml.IdpCertificate) + assert.EqualValues(t, "idpMetadataURL", res.Saml.IdpMetadataURL) + assert.EqualValues(t, "spEntityId", res.Saml.SpEntityID) + assert.EqualValues(t, "spACSUrl", res.Saml.SpACSUrl) + assert.EqualValues(t, "spCertificate", res.Saml.SpCertificate) + assert.EqualValues(t, "email", res.Saml.AttributeMapping.Email) + assert.EqualValues(t, "group", res.Saml.AttributeMapping.Group) + assert.EqualValues(t, "name", res.Saml.AttributeMapping.Name) + assert.EqualValues(t, "phoneNumber", res.Saml.AttributeMapping.PhoneNumber) + require.Len(t, res.Saml.GroupsMapping, 1) + assert.EqualValues(t, []string{"group1"}, res.Saml.GroupsMapping[0].Groups) + assert.EqualValues(t, "role.id", res.Saml.GroupsMapping[0].Role.ID) + assert.EqualValues(t, "role.name", res.Saml.GroupsMapping[0].Role.Name) + assert.EqualValues(t, "redirectURL", res.Saml.RedirectURL) + + require.NotNil(t, res.Oidc) + assert.EqualValues(t, "myName", res.Oidc.Name) + assert.EqualValues(t, "abcdef", res.Oidc.ClientID) + assert.EqualValues(t, "http://dummy.com", res.Oidc.AuthURL) + assert.EqualValues(t, "http://dummy.com", res.Oidc.TokenURL) + assert.EqualValues(t, "http://dummy.com", res.Oidc.UserDataURL) + require.NotNil(t, res.Oidc.AttributeMapping) + assert.EqualValues(t, "myGivenName", res.Oidc.AttributeMapping.GivenName) +} + +func TestLoadSettingsError(t *testing.T) { + tenantID := "abc" + mgmt := newTestMgmt(nil, helpers.DoBadRequest(func(r *http.Request) { + require.Equal(t, r.Header.Get("Authorization"), "Bearer a:key") + params := helpers.ReadParams(r) + require.Equal(t, tenantID, params["tenantId"]) + })) + res, err := mgmt.SSO().LoadSettings(tenantID) + require.Error(t, err) + assert.Nil(t, res) +} + +func TestLoadSettingsErrorMissingTenantID(t *testing.T) { + mgmt := newTestMgmt(nil, helpers.DoBadRequest(func(r *http.Request) {})) + res, err := mgmt.SSO().LoadSettings("") + require.ErrorIs(t, err, utils.NewInvalidArgumentError("tenantID")) + assert.Nil(t, res) +} + +func TestSSOConfigureSAMLSettingsSuccess(t *testing.T) { + settings := &descope.SSOSAMLSettings{ + IdpURL: "http://idpURL", + IdpEntityID: "entity", + IdpCert: "mycert", + AttributeMapping: &descope.AttributeMapping{ + GivenName: "myGivenName", + CustomAttributes: map[string]string{ + "attr1": "val1", + }, + }, + RoleMappings: []*descope.RoleMapping{ + { + Groups: []string{"grp1", "grp2"}, + Role: "role1", + }, + }, + } + mgmt := newTestMgmt(nil, helpers.DoOk(func(r *http.Request) { + require.Equal(t, r.Header.Get("Authorization"), "Bearer a:key") + req := map[string]any{} + require.NoError(t, helpers.ReadBody(r, &req)) + require.Equal(t, "abc", req["tenantId"]) + require.Equal(t, "domain.com", req["domain"]) + require.Equal(t, "https://redirect", req["redirectURL"]) + + settings, found := req["settings"] + require.True(t, found) + sett, ok := settings.(map[string]any) + require.True(t, ok) + require.Equal(t, "http://idpURL", sett["idpURL"]) + require.Equal(t, "mycert", sett["idpCert"]) + require.Equal(t, "entity", sett["entityId"]) + + userAttrMappingMap, found := sett["attributeMapping"] + require.True(t, found) + userAttrMapping, ok := userAttrMappingMap.(map[string]any) + require.True(t, ok) + require.Equal(t, "myGivenName", userAttrMapping["givenName"]) + + roleMappingMap, found := sett["roleMappings"] + require.True(t, found) + roleMappingInt, ok := roleMappingMap.([]interface{}) + require.True(t, ok) + require.Len(t, roleMappingInt, 1) + mappingMap, ok := roleMappingInt[0].(map[string]any) + require.True(t, ok) + require.Equal(t, []any{"grp1", "grp2"}, mappingMap["groups"]) + require.Equal(t, "role1", mappingMap["roleName"]) + })) + err := mgmt.SSO().ConfigureSAMLSettings("abc", settings, "https://redirect", "domain.com") + require.NoError(t, err) +} + +func TestSSOConfigureSAMLSettingsError(t *testing.T) { + mgmt := newTestMgmt(nil, helpers.DoOk(nil)) + err := mgmt.SSO().ConfigureSAMLSettings("", nil, "", "") + require.Error(t, err) + err = mgmt.SSO().ConfigureSAMLSettings("abc", nil, "", "") + require.Error(t, err) + + settings := &descope.SSOSAMLSettings{} + err = mgmt.SSO().ConfigureSAMLSettings("abc", settings, "", "") + require.Error(t, err) + + settings.IdpURL = "http://idpURL" + err = mgmt.SSO().ConfigureSAMLSettings("abc", settings, "", "") + require.Error(t, err) + + settings.IdpCert = "mycert" + err = mgmt.SSO().ConfigureSAMLSettings("abc", settings, "", "") + require.Error(t, err) +} + +func TestSSOConfigureSAMLSettingsByMetadataSuccess(t *testing.T) { + settings := &descope.SSOSAMLSettingsByMetadata{ + IdpMetadataURL: "http://idpURL", + AttributeMapping: &descope.AttributeMapping{ + GivenName: "myGivenName", + CustomAttributes: map[string]string{ + "attr1": "val1", + }, + }, + RoleMappings: []*descope.RoleMapping{ + { + Groups: []string{"grp1", "grp2"}, + Role: "role1", + }, + }, + } + mgmt := newTestMgmt(nil, helpers.DoOk(func(r *http.Request) { + require.Equal(t, r.Header.Get("Authorization"), "Bearer a:key") + req := map[string]any{} + require.NoError(t, helpers.ReadBody(r, &req)) + require.Equal(t, "abc", req["tenantId"]) + require.Equal(t, "https://redirect", req["redirectURL"]) + require.Equal(t, "domain.com", req["domain"]) + + settings, found := req["settings"] + require.True(t, found) + sett, ok := settings.(map[string]any) + require.True(t, ok) + require.Equal(t, "http://idpURL", sett["idpMetadataURL"]) + + userAttrMappingMap, found := sett["attributeMapping"] + require.True(t, found) + userAttrMapping, ok := userAttrMappingMap.(map[string]any) + require.True(t, ok) + require.Equal(t, "myGivenName", userAttrMapping["givenName"]) + + roleMappingMap, found := sett["roleMappings"] + require.True(t, found) + roleMappingInt, ok := roleMappingMap.([]interface{}) + require.True(t, ok) + require.Len(t, roleMappingInt, 1) + mappingMap, ok := roleMappingInt[0].(map[string]any) + require.True(t, ok) + require.Equal(t, []any{"grp1", "grp2"}, mappingMap["groups"]) + require.Equal(t, "role1", mappingMap["roleName"]) + })) + err := mgmt.SSO().ConfigureSAMLSettingsByMetadata("abc", settings, "https://redirect", "domain.com") + require.NoError(t, err) +} + +func TestSSOConfigureSAMLSettingsByMetadataError(t *testing.T) { + mgmt := newTestMgmt(nil, helpers.DoOk(nil)) + err := mgmt.SSO().ConfigureSAMLSettingsByMetadata("", nil, "https://redirect", "domain.com") + require.Error(t, err) + + err = mgmt.SSO().ConfigureSAMLSettingsByMetadata("abc", nil, "https://redirect", "domain.com") + require.Error(t, err) + + settings := &descope.SSOSAMLSettingsByMetadata{} + err = mgmt.SSO().ConfigureSAMLSettingsByMetadata("", settings, "https://redirect", "domain.com") + require.Error(t, err) +} + +func TestSSOConfigureOIDCSettingsSuccess(t *testing.T) { + oidcSettings := &descope.SSOOIDCSettings{ + Name: "name", + ClientID: "clientId", + AuthURL: "http://dummy.com", + TokenURL: "http://dummy.com", + UserDataURL: "http://dummy.com", + AttributeMapping: &descope.OIDCAttributeMapping{ + GivenName: "myGivenName", + }, + } + mgmt := newTestMgmt(nil, helpers.DoOk(func(r *http.Request) { + require.Equal(t, r.Header.Get("Authorization"), "Bearer a:key") + req := map[string]any{} + require.NoError(t, helpers.ReadBody(r, &req)) + require.Equal(t, "abc", req["tenantId"]) + require.Equal(t, "https://redirect", req["redirectURL"]) + require.Equal(t, "domain.com", req["domain"]) + + settings, found := req["settings"] + require.True(t, found) + sett, ok := settings.(map[string]any) + require.True(t, ok) + require.Equal(t, "name", sett["name"]) + require.Equal(t, "clientId", sett["clientId"]) + require.Equal(t, "http://dummy.com", sett["authUrl"]) + require.Equal(t, "http://dummy.com", sett["tokenUrl"]) + require.Equal(t, "http://dummy.com", sett["userDataUrl"]) + + userAttrMappingInt, found := sett["attributeMapping"] + require.True(t, found) + userAttrMapping, ok := userAttrMappingInt.(map[string]any) + require.Equal(t, "myGivenName", userAttrMapping["givenName"]) + })) + err := mgmt.SSO().ConfigureOIDCSettings("abc", oidcSettings, "https://redirect", "domain.com") + require.NoError(t, err) +} + +func TestSSOConfigureOIDCSettingsError(t *testing.T) { + mgmt := newTestMgmt(nil, helpers.DoOk(nil)) + err := mgmt.SSO().ConfigureOIDCSettings("", nil, "", "") + require.Error(t, err) + err = mgmt.SSO().ConfigureOIDCSettings("abc", nil, "", "") + require.Error(t, err) +} diff --git a/descope/internal/mgmt/tenant.go b/descope/internal/mgmt/tenant.go index 380a0cec..06994350 100644 --- a/descope/internal/mgmt/tenant.go +++ b/descope/internal/mgmt/tenant.go @@ -133,5 +133,6 @@ func makeSearchTenantRequest(options *descope.TenantSearchOptions) map[string]an "tenantNames": options.Names, "tenantSelfProvisioningDomains": options.SelfProvisioningDomains, "customAttributes": options.CustomAttributes, + "authType": options.AuthType, } } diff --git a/descope/sdk/auth.go b/descope/sdk/auth.go index 3788b57f..96d4ca38 100644 --- a/descope/sdk/auth.go +++ b/descope/sdk/auth.go @@ -187,6 +187,7 @@ type OAuth interface { ExchangeToken(code string, w http.ResponseWriter) (*descope.AuthenticationInfo, error) } +/* Deprecated */ type SAML interface { // Start will initiate a SAML login flow // return will be the redirect URL that needs to return to client @@ -198,6 +199,18 @@ type SAML interface { ExchangeToken(code string, w http.ResponseWriter) (*descope.AuthenticationInfo, error) } +type SSOSP interface { + // Start will initiate a login flow based on tenant configuration (saml/oidc) + // return will be the redirect URL that needs to return to client + // and finalize with the ExchangeToken call + // prompt argument relevant only in case tenant configured with AuthType OIDC + Start(tenant string, returnURL string, prompt string, r *http.Request, loginOptions *descope.LoginOptions, w http.ResponseWriter) (redirectURL string, err error) + + // ExchangeToken - Finalize tenant login authentication + // code should be extracted from the redirect URL of SAML/OIDC authentication flow + ExchangeToken(code string, w http.ResponseWriter) (*descope.AuthenticationInfo, error) +} + type WebAuthn interface { // SignUpStart - Use to start an authentication process with webauthn for the new user argument. // Origin is the origin of the URL for the web page where the webauthn operation is taking place, as returned @@ -250,6 +263,7 @@ type Authentication interface { Password() Password OAuth() OAuth SAML() SAML + SSO() SSOSP WebAuthn() WebAuthn // ValidateSessionWithRequest - Use to validate a session of a given request. diff --git a/descope/sdk/mgmt.go b/descope/sdk/mgmt.go index 31d9f511..2a25108b 100644 --- a/descope/sdk/mgmt.go +++ b/descope/sdk/mgmt.go @@ -314,14 +314,53 @@ type AccessKey interface { // Provides functions for configuring SSO for a project. type SSO interface { - // Get SSO setting for a tenant. + // Load all tenant SSO setting. // // tenantID is required. - GetSettings(tenantID string) (*descope.SSOSettingsResponse, error) + LoadSettings(tenantID string) (*descope.SSOTenantSettingsResponse, error) + + // Configure SSO SAML settings for a tenant manually. + // + // tenantID, settings are required (all settings parameters are required). + // + // redirectURL is optional, however if not given it has to be set when starting an SSO authentication via the request. + // domain is optional, it is used to map users to this tenant when authenticating via SSO. + // + // Both optional values will override whatever is currently set even if left empty. + ConfigureSAMLSettings(tenantID string, settings *descope.SSOSAMLSettings, redirectURL string, domain string) error + + // Configure SSO SAML settings for a tenant by fetching them from an IDP metadata URL. + // + // tenantID, settings are required (all settings parameters are required). + // + // redirectURL is optional, however if not given it has to be set when starting an SSO authentication via the request. + // domain is optional, it is used to map users to this tenant when authenticating via SSO. + // + // Both optional values will override whatever is currently set even if left empty. + ConfigureSAMLSettingsByMetadata(tenantID string, settings *descope.SSOSAMLSettingsByMetadata, redirectURL string, domain string) error + + // Configure SSO OIDC settings for a tenant manually. + // + // tenantID, settings are required. + // + // redirectURL is optional, however if not given it has to be set when starting an SSO authentication via the request. + // domain is optional, it is used to map users to this tenant when authenticating via SSO. + // + // Both optional values will override whatever is currently set even if left empty. + ConfigureOIDCSettings(tenantID string, settings *descope.SSOOIDCSettings, redirectURL string, domain string) error // tenantID is required. DeleteSettings(tenantID string) error + // *** Deprecated *** + + // Deprecated (use LoadSettings() instead) + // Get SAML SSO setting for a tenant. + // + // tenantID is required. + GetSettings(tenantID string) (*descope.SSOSettingsResponse, error) + + // Deprecated (use ConfigureSAMLSettings() instead) // Configure SSO settings for a tenant manually. // // tenantID, idpURL, idpCert, entityID, are required. The idpURL is the URL for the identity provider and idpCert @@ -333,6 +372,7 @@ type SSO interface { // Both optional values will override whatever is currently set even if left empty. ConfigureSettings(tenantID, idpURL, idpCert, entityID, redirectURL, domain string) error + // Deprecated (use ConfigureSAMLSettingsByMetadata() instead) // Configure SSO settings for a tenant by fetching them from an IDP metadata URL. // // redirectURL is optional, however if not given it has to be set when starting an SSO authentication via the request. @@ -341,6 +381,7 @@ type SSO interface { // Both optional values will override whatever is currently set even if left empty. ConfigureMetadata(tenantID, idpMetadataURL, redirectURL, domain string) error + // Deprecated (use ConfigureSAMLSettings() or ConfigureSAMLSettingsByMetadata() instead) // Configure SSO IDP mapping including groups to the Descope roles and user attributes. ConfigureMapping(tenantID string, roleMappings []*descope.RoleMapping, attributeMapping *descope.AttributeMapping) error } diff --git a/descope/tests/mocks/auth/authenticationmock.go b/descope/tests/mocks/auth/authenticationmock.go index 45374fa9..03f0a5be 100644 --- a/descope/tests/mocks/auth/authenticationmock.go +++ b/descope/tests/mocks/auth/authenticationmock.go @@ -15,6 +15,7 @@ type MockAuthentication struct { *MockPassword *MockOAuth *MockSAML + *MockSSO *MockWebAuthn MockSession } @@ -47,6 +48,10 @@ func (m *MockAuthentication) SAML() sdk.SAML { return m.MockSAML } +func (m *MockAuthentication) SSO() sdk.SSOSP { + return m.MockSSO +} + func (m *MockAuthentication) WebAuthn() sdk.WebAuthn { return m.MockWebAuthn } @@ -403,6 +408,32 @@ func (m *MockSAML) ExchangeToken(code string, w http.ResponseWriter) (*descope.A return m.ExchangeTokenResponse, m.ExchangeTokenError } +// Mock SSO + +type MockSSO struct { + StartAssert func(tenant string, returnURL string, prompt string, r *http.Request, loginOptions *descope.LoginOptions, w http.ResponseWriter) + StartError error + StartResponse string + + ExchangeTokenAssert func(code string, w http.ResponseWriter) + ExchangeTokenError error + ExchangeTokenResponse *descope.AuthenticationInfo +} + +func (m *MockSSO) Start(tenant string, returnURL string, prompt string, r *http.Request, loginOptions *descope.LoginOptions, w http.ResponseWriter) (redirectURL string, err error) { + if m.StartAssert != nil { + m.StartAssert(tenant, returnURL, prompt, r, loginOptions, w) + } + return m.StartResponse, m.StartError +} + +func (m *MockSSO) ExchangeToken(code string, w http.ResponseWriter) (*descope.AuthenticationInfo, error) { + if m.ExchangeTokenAssert != nil { + m.ExchangeTokenAssert(code, w) + } + return m.ExchangeTokenResponse, m.ExchangeTokenError +} + // Mock WebAuthn type MockWebAuthn struct { diff --git a/descope/tests/mocks/mgmt/managementmock.go b/descope/tests/mocks/mgmt/managementmock.go index 1ddd0a78..deecbec0 100644 --- a/descope/tests/mocks/mgmt/managementmock.go +++ b/descope/tests/mocks/mgmt/managementmock.go @@ -86,13 +86,26 @@ func (m *MockJWT) UpdateJWTWithCustomClaims(jwt string, customClaims map[string] // Mock SSO type MockSSO struct { - GetSettingsAssert func(tenantID string) - GetSettingsResponse *descope.SSOSettingsResponse - GetSettingsError error + LoadSettingsAssert func(tenantID string) + LoadSettingsResponse *descope.SSOTenantSettingsResponse + LoadSettingsError error + + ConfigureSAMLSettingsAssert func(tenantID string, settings *descope.SSOSAMLSettings, redirectURL string, domain string) + ConfigureSAMLSettingsError error + + ConfigureSAMLSettingsByMetadataAssert func(tenantID string, settings *descope.SSOSAMLSettingsByMetadata, redirectURL string, domain string) + ConfigureSAMLSettingsByMetadataError error + + ConfigureOIDCSettingsAssert func(tenantID string, settings *descope.SSOOIDCSettings, redirectURL, domain string) error + ConfigureOIDCSettingsError error DeleteSettingsAssert func(tenantID string) DeleteSettingsError error + GetSettingsAssert func(tenantID string) + GetSettingsResponse *descope.SSOSettingsResponse + GetSettingsError error + ConfigureSettingsAssert func(tenantID, idpURL, idpCert, entityID, redirectURL, domain string) ConfigureSettingsError error @@ -103,11 +116,32 @@ type MockSSO struct { ConfigureMappingError error } -func (m *MockSSO) GetSettings(tenantID string) (*descope.SSOSettingsResponse, error) { - if m.GetSettingsAssert != nil { - m.GetSettingsAssert(tenantID) +func (m *MockSSO) LoadSettings(tenantID string) (*descope.SSOTenantSettingsResponse, error) { + if m.LoadSettingsAssert != nil { + m.LoadSettingsAssert(tenantID) } - return m.GetSettingsResponse, m.GetSettingsError + return m.LoadSettingsResponse, m.LoadSettingsError +} + +func (m *MockSSO) ConfigureSAMLSettings(tenantID string, settings *descope.SSOSAMLSettings, redirectURL string, domain string) error { + if m.ConfigureSAMLSettingsAssert != nil { + m.ConfigureSAMLSettingsAssert(tenantID, settings, redirectURL, domain) + } + return m.ConfigureSAMLSettingsError +} + +func (m *MockSSO) ConfigureSAMLSettingsByMetadata(tenantID string, settings *descope.SSOSAMLSettingsByMetadata, redirectURL, domain string) error { + if m.ConfigureSAMLSettingsByMetadataAssert != nil { + m.ConfigureSAMLSettingsByMetadataAssert(tenantID, settings, redirectURL, domain) + } + return m.ConfigureSAMLSettingsByMetadataError +} + +func (m *MockSSO) ConfigureOIDCSettings(tenantID string, settings *descope.SSOOIDCSettings, redirectURL, domain string) error { + if m.ConfigureOIDCSettingsAssert != nil { + m.ConfigureOIDCSettingsAssert(tenantID, settings, redirectURL, domain) + } + return m.ConfigureOIDCSettingsError } func (m *MockSSO) DeleteSettings(tenantID string) error { @@ -117,6 +151,13 @@ func (m *MockSSO) DeleteSettings(tenantID string) error { return m.DeleteSettingsError } +func (m *MockSSO) GetSettings(tenantID string) (*descope.SSOSettingsResponse, error) { + if m.GetSettingsAssert != nil { + m.GetSettingsAssert(tenantID) + } + return m.GetSettingsResponse, m.GetSettingsError +} + func (m *MockSSO) ConfigureSettings(tenantID, idpURL, idpCert, entityID, redirectURL, domain string) error { if m.ConfigureSettingsAssert != nil { m.ConfigureSettingsAssert(tenantID, idpURL, idpCert, entityID, redirectURL, domain) diff --git a/descope/types.go b/descope/types.go index 6d32e7f1..b0df5502 100644 --- a/descope/types.go +++ b/descope/types.go @@ -68,6 +68,71 @@ type SSOSettingsResponse struct { Domain string `json:"domain,omitempty"` } +type SSOSAMLSettingsResponse struct { + IdpEntityID string `json:"idpEntityId,omitempty"` + IdpSSOURL string `json:"idpSSOUrl,omitempty"` + IdpCertificate string `json:"idpCertificate,omitempty"` + IdpMetadataURL string `json:"idpMetadataUrl,omitempty"` + SpEntityID string `json:"spEntityId,omitempty"` + SpACSUrl string `json:"spACSUrl,omitempty"` + SpCertificate string `json:"spCertificate,omitempty"` + AttributeMapping *AttributeMapping `json:"attributeMapping,omitempty"` + GroupsMapping []*GroupsMapping `json:"groupsMapping,omitempty"` + RedirectURL string `json:"redirectUrl,omitempty"` +} + +type SSOSAMLSettings struct { + IdpURL string `json:"idpURL,omitempty"` + IdpEntityID string `json:"entityId,omitempty"` + IdpCert string `json:"idpCert,omitempty"` + AttributeMapping *AttributeMapping `json:"attributeMapping,omitempty"` + RoleMappings []*RoleMapping `json:"roleMappings,omitempty"` +} + +type SSOSAMLSettingsByMetadata struct { + IdpMetadataURL string `json:"idpMetadataUrl,omitempty"` + AttributeMapping *AttributeMapping `json:"attributeMapping,omitempty"` + RoleMappings []*RoleMapping `json:"roleMappings,omitempty"` +} + +type OIDCAttributeMapping struct { + ExternalID string `json:"externalID,omitempty"` + Name string `json:"name,omitempty"` + GivenName string `json:"givenName,omitempty"` + MiddleName string `json:"middleName,omitempty"` + FamilyName string `json:"familyName,omitempty"` + Email string `json:"email,omitempty"` + VerifiedEmail string `json:"verifiedEmail,omitempty"` + Username string `json:"username,omitempty"` + PhoneNumber string `json:"phoneNumber,omitempty"` + VerifiedPhone string `json:"verifiedPhone,omitempty"` + Picture string `json:"picture,omitempty"` +} + +type SSOOIDCSettings struct { + Name string `json:"name,omitempty"` + ClientID string `json:"clientId,omitempty"` + ClientSecret string `json:"clientSecret,omitempty"` // will be empty on response + RedirectUrl string `json:"redirectUrl,omitempty"` + AuthURL string `json:"authUrl,omitempty"` + TokenURL string `json:"tokenUrl,omitempty"` + UserDataURL string `json:"userDataUrl,omitempty"` + Scope []string `json:"scope,omitempty"` + JWKsURL string `json:"JWKsUrl,omitempty"` + AttributeMapping *OIDCAttributeMapping `json:"userAttrMapping,omitempty"` + ManageProviderTokens bool `json:"manageProviderTokens,omitempty"` + CallbackDomain string `json:"callbackDomain,omitempty"` + Prompt []string `json:"prompt,omitempty"` + GrantType string `json:"grantType,omitempty"` + Issuer string `json:"issuer,omitempty"` +} + +type SSOTenantSettingsResponse struct { + Tenant *Tenant `json:"tenant,omitempty"` + Saml *SSOSAMLSettingsResponse `json:"saml,omitempty"` + Oidc *SSOOIDCSettings `json:"oidc,omitempty"` +} + // PasswordPolicy - represents the rules for valid passwords configured in the policy // in the Descope console. This can be used to implement client-side validation of new // user passwords for a better user experience. Either way, the comprehensive @@ -387,16 +452,17 @@ type RoleMapping struct { Role string } -// Represents a mapping between Descope and IDP user attributes +// Represents a SAML mapping between Descope and IDP user attributes type AttributeMapping struct { - Name string `json:"name,omitempty"` - GivenName string `json:"givenName,omitempty"` - MiddleName string `json:"middleName,omitempty"` - FamilyName string `json:"familyName,omitempty"` - Picture string `json:"picture,omitempty"` - Email string `json:"email,omitempty"` - PhoneNumber string `json:"phoneNumber,omitempty"` - Group string `json:"group,omitempty"` + Name string `json:"name,omitempty"` + GivenName string `json:"givenName,omitempty"` + MiddleName string `json:"middleName,omitempty"` + FamilyName string `json:"familyName,omitempty"` + Picture string `json:"picture,omitempty"` + Email string `json:"email,omitempty"` + PhoneNumber string `json:"phoneNumber,omitempty"` + Group string `json:"group,omitempty"` + CustomAttributes map[string]string `json:"customAttributes,omitempty"` } type Tenant struct { @@ -404,6 +470,8 @@ type Tenant struct { Name string `json:"name"` SelfProvisioningDomains []string `json:"selfProvisioningDomains"` CustomAttributes map[string]any `json:"customAttributes,omitempty"` + AuthType string `json:"authType,omitempty"` + Domain string `json:"domain,omitempty"` } type TenantRequest struct { @@ -417,6 +485,7 @@ type TenantSearchOptions struct { Names []string SelfProvisioningDomains []string CustomAttributes map[string]any + AuthType string } type Permission struct { From 1c166e61bdcf72f58a3bdb252d96f2de7dcf3e81 Mon Sep 17 00:00:00 2001 From: guyp-descope Date: Wed, 20 Dec 2023 22:38:22 +0200 Subject: [PATCH 2/9] fix mock + deprecation comments style --- README.md | 10 +++++----- descope/sdk/mgmt.go | 8 ++++---- descope/tests/mocks/auth/authenticationmock.go | 4 ++-- descope/types.go | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index dad40038..5bb0101b 100644 --- a/README.md +++ b/README.md @@ -262,7 +262,7 @@ if err != nil { ```go -// Deprecated +//* Deprecated *// // // Choose which tenant to log into // If configured globally, the return URL is optional. If provided however, it will be used @@ -286,7 +286,7 @@ if err != nil { ``` ```go -// Deprecated +//* Deprecated *// // // The optional `w http.ResponseWriter` adds the session and refresh cookies to the response automatically. // Otherwise they're available via authInfo @@ -783,7 +783,7 @@ You can manage SSO SAML or OIDC settings for a specific tenant. // Load all tenant SSO settings ssoSettings, err := cc.HC.DescopeClient().Management.SSO().LoadSettings(context.Background(), "tenant-id") -// Deprecated +//* Deprecated *// ssoSettings, err := descopeClient.Management.SSO().GetSettings(context.Background(), "tenant-id") // Configure tenant SSO by OIDC settings @@ -815,7 +815,7 @@ samlSettings := &descope.SSOSAMLSettings{ } err = cc.HC.DescopeClient().Management.SSO().ConfigureSAMLSettings(context.Background(), tenantID, samlSettings, redirectURL, domain) -// Deprecated +//* Deprecated *// err := descopeClient.Management.SSO().ConfigureSettings(context.Background(), tenantID, idpURL, entityID, idpCert, redirectURL, domain) // Alternatively, configure using an SSO SAML metadata URL @@ -826,7 +826,7 @@ samlSettings := &descope.SSOSAMLSettingsByMetadata{ } err = cc.HC.DescopeClient().Management.SSO().ConfigureSAMLSettingsByMetadata(context.Background(), tenantID, samlSettings, redirectURL, domain) -// Deprecated +//* Deprecated *// err := descopeClient.Management.SSO().ConfigureMetadata(tenantID, "https://idp.com/my-idp-metadata", redirectURL, domain) // Map IDP groups to Descope roles, or map user attributes. diff --git a/descope/sdk/mgmt.go b/descope/sdk/mgmt.go index 197fa1a3..1341c7ad 100644 --- a/descope/sdk/mgmt.go +++ b/descope/sdk/mgmt.go @@ -376,13 +376,13 @@ type SSO interface { // *** Deprecated *** - // Deprecated (use LoadSettings() instead) + //* Deprecated (use LoadSettings() instead) *// // Get SAML SSO setting for a tenant. // // tenantID is required. GetSettings(_ context.Context, tenantID string) (*descope.SSOSettingsResponse, error) - // Deprecated (use ConfigureSAMLSettings() instead) + //* Deprecated (use ConfigureSAMLSettings() instead) *// // Configure SSO settings for a tenant manually. // // tenantID, idpURL, idpCert, entityID, are required. The idpURL is the URL for the identity provider and idpCert @@ -394,7 +394,7 @@ type SSO interface { // Both optional values will override whatever is currently set even if left empty. ConfigureSettings(ctx context.Context, tenantID, idpURL, idpCert, entityID, redirectURL, domain string) error - // Deprecated (use ConfigureSAMLSettingsByMetadata() instead) + //* Deprecated (use ConfigureSAMLSettingsByMetadata() instead) *// // Configure SSO settings for a tenant by fetching them from an IDP metadata URL. // // redirectURL is optional, however if not given it has to be set when starting an SSO authentication via the request. @@ -403,7 +403,7 @@ type SSO interface { // Both optional values will override whatever is currently set even if left empty. ConfigureMetadata(ctx context.Context, tenantID, idpMetadataURL, redirectURL, domain string) error - // Deprecated (use ConfigureSAMLSettings() or ConfigureSAMLSettingsByMetadata() instead) + //* Deprecated (use ConfigureSAMLSettings() or ConfigureSAMLSettingsByMetadata() instead) *// // Configure SSO IDP mapping including groups to the Descope roles and user attributes. ConfigureMapping(ctx context.Context, tenantID string, roleMappings []*descope.RoleMapping, attributeMapping *descope.AttributeMapping) error } diff --git a/descope/tests/mocks/auth/authenticationmock.go b/descope/tests/mocks/auth/authenticationmock.go index f8b75536..4e13aa2e 100644 --- a/descope/tests/mocks/auth/authenticationmock.go +++ b/descope/tests/mocks/auth/authenticationmock.go @@ -421,14 +421,14 @@ type MockSSO struct { ExchangeTokenResponse *descope.AuthenticationInfo } -func (m *MockSSO) Start(tenant string, returnURL string, prompt string, r *http.Request, loginOptions *descope.LoginOptions, w http.ResponseWriter) (redirectURL string, err error) { +func (m *MockSSO) Start(_ context.Context, tenant string, returnURL string, prompt string, r *http.Request, loginOptions *descope.LoginOptions, w http.ResponseWriter) (redirectURL string, err error) { if m.StartAssert != nil { m.StartAssert(tenant, returnURL, prompt, r, loginOptions, w) } return m.StartResponse, m.StartError } -func (m *MockSSO) ExchangeToken(code string, w http.ResponseWriter) (*descope.AuthenticationInfo, error) { +func (m *MockSSO) ExchangeToken(_ context.Context, code string, w http.ResponseWriter) (*descope.AuthenticationInfo, error) { if m.ExchangeTokenAssert != nil { m.ExchangeTokenAssert(code, w) } diff --git a/descope/types.go b/descope/types.go index b0df5502..ae6f18d6 100644 --- a/descope/types.go +++ b/descope/types.go @@ -113,7 +113,7 @@ type SSOOIDCSettings struct { Name string `json:"name,omitempty"` ClientID string `json:"clientId,omitempty"` ClientSecret string `json:"clientSecret,omitempty"` // will be empty on response - RedirectUrl string `json:"redirectUrl,omitempty"` + RedirectURL string `json:"redirectUrl,omitempty"` AuthURL string `json:"authUrl,omitempty"` TokenURL string `json:"tokenUrl,omitempty"` UserDataURL string `json:"userDataUrl,omitempty"` From 2b61dd1ca61983364546ede96592f10d96179c37 Mon Sep 17 00:00:00 2001 From: guyp-descope Date: Wed, 20 Dec 2023 22:55:46 +0200 Subject: [PATCH 3/9] adjust comments --- README.md | 7 ++++--- descope/api/client.go | 7 +++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5bb0101b..32cef7a7 100644 --- a/README.md +++ b/README.md @@ -783,7 +783,7 @@ You can manage SSO SAML or OIDC settings for a specific tenant. // Load all tenant SSO settings ssoSettings, err := cc.HC.DescopeClient().Management.SSO().LoadSettings(context.Background(), "tenant-id") -//* Deprecated *// +//* Deprecated (use LoadSettings(..) instead) *// ssoSettings, err := descopeClient.Management.SSO().GetSettings(context.Background(), "tenant-id") // Configure tenant SSO by OIDC settings @@ -815,7 +815,7 @@ samlSettings := &descope.SSOSAMLSettings{ } err = cc.HC.DescopeClient().Management.SSO().ConfigureSAMLSettings(context.Background(), tenantID, samlSettings, redirectURL, domain) -//* Deprecated *// +//* Deprecated (use ConfigureSAMLSettings(..) instead) *// err := descopeClient.Management.SSO().ConfigureSettings(context.Background(), tenantID, idpURL, entityID, idpCert, redirectURL, domain) // Alternatively, configure using an SSO SAML metadata URL @@ -826,9 +826,10 @@ samlSettings := &descope.SSOSAMLSettingsByMetadata{ } err = cc.HC.DescopeClient().Management.SSO().ConfigureSAMLSettingsByMetadata(context.Background(), tenantID, samlSettings, redirectURL, domain) -//* Deprecated *// +//* Deprecated (use ConfigureSAMLSettingsByMetadata(..) instead) *// err := descopeClient.Management.SSO().ConfigureMetadata(tenantID, "https://idp.com/my-idp-metadata", redirectURL, domain) +//* Deprecated *// // Map IDP groups to Descope roles, or map user attributes. // This function overrides any previous mapping (even when empty). Use carefully. roleMapping := []*descope.RoleMapping{ diff --git a/descope/api/client.go b/descope/api/client.go index 4c735a3d..1aa8e2f2 100644 --- a/descope/api/client.go +++ b/descope/api/client.go @@ -277,11 +277,12 @@ type mgmtEndpoints struct { accessKeyActivate string accessKeyDelete string - // // Deprecated + //* Deprecated *// ssoSettings string ssoMetadata string ssoMapping string - //////////////////////////////// + /////////////////// + ssoLoadSettings string ssoSAMLSettings string ssoSAMLSettingsByMetadata string @@ -677,8 +678,6 @@ func (e *endpoints) ManagementSSOMapping() string { return path.Join(e.version, e.mgmt.ssoMapping) } -//////////////////////////////////// - func (e *endpoints) ManagementUpdateJWT() string { return path.Join(e.version, e.mgmt.updateJWT) } From 89f42d43f982240d3a8b5ded5ebca1ab34df05d7 Mon Sep 17 00:00:00 2001 From: guyp-descope Date: Wed, 20 Dec 2023 23:07:06 +0200 Subject: [PATCH 4/9] fix tests --- descope/internal/mgmt/sso_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/descope/internal/mgmt/sso_test.go b/descope/internal/mgmt/sso_test.go index 1262a430..e5c21684 100644 --- a/descope/internal/mgmt/sso_test.go +++ b/descope/internal/mgmt/sso_test.go @@ -236,7 +236,7 @@ func TestLoadSettingsSuccess(t *testing.T) { "authUrl": "http://dummy.com", "tokenUrl": "http://dummy.com", "userDataUrl": "http://dummy.com", - "attributeMapping": map[string]any{ + "userAttrMapping": map[string]any{ "givenName": "myGivenName", }, }, @@ -465,7 +465,7 @@ func TestSSOConfigureOIDCSettingsSuccess(t *testing.T) { require.Equal(t, "http://dummy.com", sett["tokenUrl"]) require.Equal(t, "http://dummy.com", sett["userDataUrl"]) - userAttrMappingInt, found := sett["attributeMapping"] + userAttrMappingInt, found := sett["userAttrMapping"] require.True(t, found) userAttrMapping, ok := userAttrMappingInt.(map[string]any) require.Equal(t, "myGivenName", userAttrMapping["givenName"]) From 67979e973f6f399bccca1c1bd10455151b600eba Mon Sep 17 00:00:00 2001 From: guyp-descope Date: Sun, 24 Dec 2023 09:58:17 +0200 Subject: [PATCH 5/9] fix pr issues --- descope/api/client.go | 6 +++--- descope/internal/auth/auth.go | 4 ++-- descope/internal/mgmt/sso.go | 2 ++ descope/sdk/auth.go | 4 ++-- descope/tests/mocks/auth/authenticationmock.go | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/descope/api/client.go b/descope/api/client.go index 1aa8e2f2..a70d5583 100644 --- a/descope/api/client.go +++ b/descope/api/client.go @@ -277,7 +277,7 @@ type mgmtEndpoints struct { accessKeyActivate string accessKeyDelete string - //* Deprecated *// + //* Deprecated (use the below value instead) *// ssoSettings string ssoMetadata string ssoMapping string @@ -411,12 +411,12 @@ func (e *endpoints) ExchangeTokenOAuth() string { return path.Join(e.version, e.auth.exchangeTokenOAuth) } -/* Deprecated */ +/* Deprecated (use SSOStart(..) instead) */ func (e *endpoints) SAMLStart() string { return path.Join(e.version, e.auth.samlStart) } -/* Deprecated */ +/* Deprecated (use ExchangeTokenSSO(..) instead) */ func (e *endpoints) ExchangeTokenSAML() string { return path.Join(e.version, e.auth.exchangeTokenSAML) } diff --git a/descope/internal/auth/auth.go b/descope/internal/auth/auth.go index 9824fdf5..1bb126fb 100644 --- a/descope/internal/auth/auth.go +++ b/descope/internal/auth/auth.go @@ -44,7 +44,7 @@ type authenticationService struct { webAuthn sdk.WebAuthn oauth sdk.OAuth saml sdk.SAML - sso sdk.SSOSP + sso sdk.SSOServiceProvider } func NewAuth(conf AuthParams, c *api.Client) (*authenticationService, error) { @@ -91,7 +91,7 @@ func (auth *authenticationService) SAML() sdk.SAML { return auth.saml } -func (auth *authenticationService) SSO() sdk.SSOSP { +func (auth *authenticationService) SSO() sdk.SSOServiceProvider { return auth.sso } diff --git a/descope/internal/mgmt/sso.go b/descope/internal/mgmt/sso.go index 83e03b1a..73f2f7f6 100644 --- a/descope/internal/mgmt/sso.go +++ b/descope/internal/mgmt/sso.go @@ -39,9 +39,11 @@ func (s *sso) ConfigureSAMLSettings(ctx context.Context, tenantID string, settin if settings.IdpURL == "" { return utils.NewInvalidArgumentError("idpURL") } + if settings.IdpCert == "" { return utils.NewInvalidArgumentError("idpCert") } + if settings.IdpEntityID == "" { return utils.NewInvalidArgumentError("idpEntityID") } diff --git a/descope/sdk/auth.go b/descope/sdk/auth.go index 78fdfe72..a12d1b74 100644 --- a/descope/sdk/auth.go +++ b/descope/sdk/auth.go @@ -200,7 +200,7 @@ type SAML interface { ExchangeToken(ctx context.Context, code string, w http.ResponseWriter) (*descope.AuthenticationInfo, error) } -type SSOSP interface { +type SSOServiceProvider interface { // Start will initiate a login flow based on tenant configuration (saml/oidc) // return will be the redirect URL that needs to return to client // and finalize with the ExchangeToken call @@ -264,7 +264,7 @@ type Authentication interface { Password() Password OAuth() OAuth SAML() SAML - SSO() SSOSP + SSO() SSOServiceProvider WebAuthn() WebAuthn // ValidateSessionWithRequest - Use to validate a session of a given request. diff --git a/descope/tests/mocks/auth/authenticationmock.go b/descope/tests/mocks/auth/authenticationmock.go index 4e13aa2e..1cb0e55c 100644 --- a/descope/tests/mocks/auth/authenticationmock.go +++ b/descope/tests/mocks/auth/authenticationmock.go @@ -49,7 +49,7 @@ func (m *MockAuthentication) SAML() sdk.SAML { return m.MockSAML } -func (m *MockAuthentication) SSO() sdk.SSOSP { +func (m *MockAuthentication) SSO() sdk.SSOServiceProvider { return m.MockSSO } From 84c2358aefac7398cbe9e99b684718bd64d32944 Mon Sep 17 00:00:00 2001 From: guyp-descope Date: Sun, 24 Dec 2023 15:53:33 +0200 Subject: [PATCH 6/9] forgot file from last commit --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 32cef7a7..e85be134 100644 --- a/README.md +++ b/README.md @@ -262,7 +262,7 @@ if err != nil { ```go -//* Deprecated *// +//* Deprecated (use Auth.SSO().Start(..) instead) *// // // Choose which tenant to log into // If configured globally, the return URL is optional. If provided however, it will be used @@ -286,7 +286,7 @@ if err != nil { ``` ```go -//* Deprecated *// +//* Deprecated (use Auth.SSO().ExchangeToken(..) instead) *// // // The optional `w http.ResponseWriter` adds the session and refresh cookies to the response automatically. // Otherwise they're available via authInfo @@ -777,12 +777,13 @@ err := descopeClient.Management.AccessKey().Delete(context.Background(), "access ### Manage SSO Setting -You can manage SSO SAML or OIDC settings for a specific tenant. +You can manage SSO (SAML or OIDC) settings for a specific tenant. ```go // Load all tenant SSO settings ssoSettings, err := cc.HC.DescopeClient().Management.SSO().LoadSettings(context.Background(), "tenant-id") + //* Deprecated (use LoadSettings(..) instead) *// ssoSettings, err := descopeClient.Management.SSO().GetSettings(context.Background(), "tenant-id") @@ -829,7 +830,7 @@ err = cc.HC.DescopeClient().Management.SSO().ConfigureSAMLSettingsByMetadata(con //* Deprecated (use ConfigureSAMLSettingsByMetadata(..) instead) *// err := descopeClient.Management.SSO().ConfigureMetadata(tenantID, "https://idp.com/my-idp-metadata", redirectURL, domain) -//* Deprecated *// +//* Deprecated (use Management.SSO().ConfigureSAMLSettings(..) or Management.SSO().ConfigureSAMLSettingsByMetadata(..) instead) *// // Map IDP groups to Descope roles, or map user attributes. // This function overrides any previous mapping (even when empty). Use carefully. roleMapping := []*descope.RoleMapping{ From 67d8200fc16dfe173d0bc64931a3124a10838d78 Mon Sep 17 00:00:00 2001 From: guyp-descope Date: Wed, 27 Dec 2023 15:26:48 +0200 Subject: [PATCH 7/9] adjust fields names --- descope/internal/mgmt/sso.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/descope/internal/mgmt/sso.go b/descope/internal/mgmt/sso.go index 6acdffcd..3617364e 100644 --- a/descope/internal/mgmt/sso.go +++ b/descope/internal/mgmt/sso.go @@ -59,13 +59,13 @@ func (s *sso) ConfigureSAMLSettings(ctx context.Context, tenantID string, settin req := map[string]any{ "tenantId": tenantID, "settings": map[string]any{ - "idpURL": settings.IdpURL, + "idpUrl": settings.IdpURL, "entityId": settings.IdpEntityID, "idpCert": settings.IdpCert, "roleMappings": mappings, "attributeMapping": settings.AttributeMapping, }, - "redirectURL": redirectURL, + "redirectUrl": redirectURL, "domains": domains, } _, err := s.client.DoPostRequest(ctx, api.Routes.ManagementSSOSAMLSettings(), req, nil, s.conf.ManagementKey) @@ -95,11 +95,11 @@ func (s *sso) ConfigureSAMLSettingsByMetadata(ctx context.Context, tenantID stri req := map[string]any{ "tenantId": tenantID, "settings": map[string]any{ - "idpMetadataURL": settings.IdpMetadataURL, + "idpMetadataUrl": settings.IdpMetadataURL, "roleMappings": mappings, "attributeMapping": settings.AttributeMapping, }, - "redirectURL": redirectURL, + "redirectUrl": redirectURL, "domains": domains, } _, err := s.client.DoPostRequest(ctx, api.Routes.ManagementSSOSAMLSettingsByMetadata(), req, nil, s.conf.ManagementKey) @@ -118,7 +118,7 @@ func (s *sso) ConfigureOIDCSettings(ctx context.Context, tenantID string, settin req := map[string]any{ "tenantId": tenantID, "settings": settings, - "redirectURL": redirectURL, + "redirectUrl": redirectURL, "domains": domains, } From 7631d317dd147542dc9f1be82aeb5d1aadb07e95 Mon Sep 17 00:00:00 2001 From: guyp-descope Date: Wed, 27 Dec 2023 15:46:13 +0200 Subject: [PATCH 8/9] fix tests --- descope/internal/mgmt/sso_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/descope/internal/mgmt/sso_test.go b/descope/internal/mgmt/sso_test.go index 195667ed..737e70af 100644 --- a/descope/internal/mgmt/sso_test.go +++ b/descope/internal/mgmt/sso_test.go @@ -329,7 +329,7 @@ func TestSSOConfigureSAMLSettingsSuccess(t *testing.T) { req := map[string]any{} require.NoError(t, helpers.ReadBody(r, &req)) require.Equal(t, "abc", req["tenantId"]) - require.Equal(t, "https://redirect", req["redirectURL"]) + require.Equal(t, "https://redirect", req["redirectUrl"]) domains := req["domains"].([]any) require.Len(t, domains, 1) @@ -339,7 +339,7 @@ func TestSSOConfigureSAMLSettingsSuccess(t *testing.T) { require.True(t, found) sett, ok := settings.(map[string]any) require.True(t, ok) - require.Equal(t, "http://idpURL", sett["idpURL"]) + require.Equal(t, "http://idpURL", sett["idpUrl"]) require.Equal(t, "mycert", sett["idpCert"]) require.Equal(t, "entity", sett["entityId"]) @@ -404,7 +404,7 @@ func TestSSOConfigureSAMLSettingsByMetadataSuccess(t *testing.T) { req := map[string]any{} require.NoError(t, helpers.ReadBody(r, &req)) require.Equal(t, "abc", req["tenantId"]) - require.Equal(t, "https://redirect", req["redirectURL"]) + require.Equal(t, "https://redirect", req["redirectUrl"]) domains := req["domains"].([]any) require.Len(t, domains, 1) @@ -414,7 +414,7 @@ func TestSSOConfigureSAMLSettingsByMetadataSuccess(t *testing.T) { require.True(t, found) sett, ok := settings.(map[string]any) require.True(t, ok) - require.Equal(t, "http://idpURL", sett["idpMetadataURL"]) + require.Equal(t, "http://idpURL", sett["idpMetadataUrl"]) userAttrMappingMap, found := sett["attributeMapping"] require.True(t, found) @@ -465,7 +465,7 @@ func TestSSOConfigureOIDCSettingsSuccess(t *testing.T) { req := map[string]any{} require.NoError(t, helpers.ReadBody(r, &req)) require.Equal(t, "abc", req["tenantId"]) - require.Equal(t, "https://redirect", req["redirectURL"]) + require.Equal(t, "https://redirect", req["redirectUrl"]) domains := req["domains"].([]any) require.Len(t, domains, 1) From ecfdcd43481d9825e083823026cdf3f16ac7a985 Mon Sep 17 00:00:00 2001 From: guyp-descope Date: Wed, 27 Dec 2023 16:14:25 +0200 Subject: [PATCH 9/9] change load settings url to use v2 endpoint + change externalId to loginId --- descope/api/client.go | 4 ++-- descope/types.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/descope/api/client.go b/descope/api/client.go index 0863311e..b3777e46 100644 --- a/descope/api/client.go +++ b/descope/api/client.go @@ -119,7 +119,7 @@ var ( accessKeyActivate: "mgmt/accesskey/activate", accessKeyDelete: "mgmt/accesskey/delete", ssoSettings: "mgmt/sso/settings", - ssoLoadSettings: "mgmt/sso/loadsettings", + ssoLoadSettings: "mgmt/sso/settings", // v2 only ssoSAMLSettings: "mgmt/sso/samlsettings", ssoSAMLSettingsByMetadata: "mgmt/sso/samlsettingsbymetadata", ssoOIDCSettings: "mgmt/sso/oidcsettings", @@ -652,7 +652,7 @@ func (e *endpoints) ManagementAccessKeyDelete() string { } func (e *endpoints) ManagementSSOLoadSettings() string { - return path.Join(e.version, e.mgmt.ssoLoadSettings) + return path.Join(e.versionV2, e.mgmt.ssoLoadSettings) } func (e *endpoints) ManagementSSOSAMLSettings() string { diff --git a/descope/types.go b/descope/types.go index a374bad9..1f8f7b10 100644 --- a/descope/types.go +++ b/descope/types.go @@ -98,7 +98,7 @@ type SSOSAMLSettingsByMetadata struct { } type OIDCAttributeMapping struct { - ExternalID string `json:"externalID,omitempty"` + LoginID string `json:"loginId,omitempty"` Name string `json:"name,omitempty"` GivenName string `json:"givenName,omitempty"` MiddleName string `json:"middleName,omitempty"`