diff --git a/examples/management-cli/pom.xml b/examples/management-cli/pom.xml
index e7bba2de..5cb24ae9 100644
--- a/examples/management-cli/pom.xml
+++ b/examples/management-cli/pom.xml
@@ -19,7 +19,7 @@
com.descope
java-sdk
- 1.0.13
+ 1.0.14
info.picocli
diff --git a/pom.xml b/pom.xml
index da62baa9..79600a0d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
com.descope
java-sdk
4.0.0
- 1.0.13
+ 1.0.14
${project.groupId}:${project.artifactId}
Java library used to integrate with Descope.
https://github.com/descope/descope-java
diff --git a/src/main/java/com/descope/exception/ServerCommonException.java b/src/main/java/com/descope/exception/ServerCommonException.java
index ea92c470..43652bbe 100644
--- a/src/main/java/com/descope/exception/ServerCommonException.java
+++ b/src/main/java/com/descope/exception/ServerCommonException.java
@@ -19,6 +19,11 @@ protected ServerCommonException(String message, String code) {
setCode(code);
}
+ protected ServerCommonException(String message, String code, Throwable cause) {
+ super(message, cause);
+ setCode(code);
+ }
+
public static ServerCommonException invalidArgument(String property) {
String message = String.format("The %s argument is invalid", property);
return new ServerCommonException(message, INVALID_ARGUMENT);
@@ -41,4 +46,10 @@ public static ServerCommonException genericServerError(String message, String co
e.serverResponse = serverResponse;
return e;
}
+
+ public static ServerCommonException parseResponseError(String message, String serverResponse, Throwable cause) {
+ ServerCommonException e = new ServerCommonException(message, null, cause);
+ e.serverResponse = serverResponse;
+ return e;
+ }
}
diff --git a/src/main/java/com/descope/literals/Routes.java b/src/main/java/com/descope/literals/Routes.java
index 616a683b..a9bf552c 100644
--- a/src/main/java/com/descope/literals/Routes.java
+++ b/src/main/java/com/descope/literals/Routes.java
@@ -54,6 +54,10 @@ public static class AuthEndPoints {
public static final String COMPOSE_SAML_START_LINK = "/v1/auth/saml/authorize";
public static final String EXCHANGE_SAML_LINK = "/v1/auth/saml/exchange";
+ // SSO
+ public static final String COMPOSE_SSO_START_LINK = "/v1/auth/sso/authorize";
+ public static final String EXCHANGE_SSO_LINK = "/v1/auth/sso/exchange";
+
// Password
public static final String SIGN_UP_PASSWORD_LINK = "/v1/auth/password/signup";
public static final String SIGN_IN_PASSWORD_LINK = "/v1/auth/password/signin";
@@ -97,6 +101,9 @@ public static class ManagementEndPoints {
public static final String USER_SET_ROLES_LINK = "/v1/mgmt/user/update/role/set";
public static final String USER_ADD_ROLES_LINK = "/v1/mgmt/user/update/role/add";
public static final String USER_REMOVE_ROLES_LINK = "/v1/mgmt/user/update/role/remove";
+ public static final String USER_SET_SSO_APPS_LINK = "/v1/mgmt/user/update/ssoapp/set";
+ public static final String USER_ADD_SSO_APPS_LINK = "/v1/mgmt/user/update/ssoapp/add";
+ public static final String USER_REMOVE_SSO_APPS_LINK = "/v1/mgmt/user/update/ssoapp/remove";
public static final String USER_ADD_TENANT_LINK = "/v1/mgmt/user/update/tenant/add";
public static final String USER_REMOVE_TENANT_LINK = "/v1/mgmt/user/update/tenant/remove";
public static final String GET_PROVIDER_TOKEN = "/v1/mgmt/user/provider/token";
@@ -118,11 +125,24 @@ public static class ManagementEndPoints {
public static final String GET_TENANT_SETTINGS_LINK = "/v1/mgmt/tenant/settings";
// SSO
- public static final String SSO_GET_SETTINGS_LINK = "/v2/mgmt/sso/settings";
+ public static final String SSO_GET_SETTINGS_LINK = "/v1/mgmt/sso/settings";
public static final String SSO_DELETE_SETTINGS_LINK = "/v1/mgmt/sso/settings";
public static final String SSO_CONFIGURE_SETTINGS_LINK = "/v1/mgmt/sso/settings";
public static final String SSO_CONFIGURE_METADATA_LINK = "/v1/mgmt/sso/metadata";
public static final String SSO_CONFIGURE_MAPPING_LINK = "/v1/mgmt/sso/mapping";
+ public static final String SSO_GET_SETTINGS_V2_LINK = "/v2/mgmt/sso/settings";
+ public static final String SSO_CONFIGURE_SAML_SETTINGS_LINK = "/v1/mgmt/sso/saml";
+ public static final String SSO_CONFIGURE_SAML_SETTINGS_BY_MD_LINK = "/v1/mgmt/sso/saml/metadata";
+ public static final String SSO_CONFIGURE_OIDC_SETTINGS_LINK = "/v1/mgmt/sso/oidc";
+
+ // SSO Application
+ public static final String SSO_APPLICATION_OIDC_CREATE_LINK = "/v1/mgmt/sso/idp/app/oidc/create";
+ public static final String SSO_APPLICATION_SAML_CREATE_LINK = "/v1/mgmt/sso/idp/app/saml/create";
+ public static final String SSO_APPLICATION_OIDC_UPDATE_LINK = "/v1/mgmt/sso/idp/app/oidc/update";
+ public static final String SSO_APPLICATION_SAML_UPDATE_LINK = "/v1/mgmt/sso/idp/app/saml/update";
+ public static final String SSO_APPLICATION_DELETE_LINK = "/v1/mgmt/sso/idp/app/delete";
+ public static final String SSO_APPLICATION_LOAD_LINK = "/v1/mgmt/sso/idp/app/load";
+ public static final String SSO_APPLICATION_LOAD_ALL_LINK = "/v1/mgmt/sso/idp/apps/load";
// Group
public static final String GROUP_LOAD_ALL_LINK = "/v1/mgmt/group/all";
diff --git a/src/main/java/com/descope/model/auth/AuthenticationServices.java b/src/main/java/com/descope/model/auth/AuthenticationServices.java
index e6a58cc2..d2cc1704 100644
--- a/src/main/java/com/descope/model/auth/AuthenticationServices.java
+++ b/src/main/java/com/descope/model/auth/AuthenticationServices.java
@@ -7,6 +7,7 @@
import com.descope.sdk.auth.OTPService;
import com.descope.sdk.auth.PasswordService;
import com.descope.sdk.auth.SAMLService;
+import com.descope.sdk.auth.SSOServiceProvider;
import com.descope.sdk.auth.TOTPService;
import com.descope.sdk.auth.WebAuthnService;
import lombok.Builder;
@@ -18,6 +19,7 @@ public class AuthenticationServices {
AuthenticationService authService;
OTPService otpService;
SAMLService samlService;
+ SSOServiceProvider ssoServiceProvider;
TOTPService totpService;
OAuthService oauthService;
PasswordService passwordService;
diff --git a/src/main/java/com/descope/model/magiclink/LoginOptions.java b/src/main/java/com/descope/model/magiclink/LoginOptions.java
index 2151a167..6d3f6778 100644
--- a/src/main/java/com/descope/model/magiclink/LoginOptions.java
+++ b/src/main/java/com/descope/model/magiclink/LoginOptions.java
@@ -14,4 +14,5 @@ public class LoginOptions {
private boolean stepup;
private boolean mfa;
private Map customClaims;
+ private Map templateOptions;
}
diff --git a/src/main/java/com/descope/model/magiclink/SignUpOptions.java b/src/main/java/com/descope/model/magiclink/SignUpOptions.java
new file mode 100644
index 00000000..1010d3b3
--- /dev/null
+++ b/src/main/java/com/descope/model/magiclink/SignUpOptions.java
@@ -0,0 +1,16 @@
+package com.descope.model.magiclink;
+
+import java.util.Map;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SignUpOptions {
+ private Map customClaims;
+ private Map templateOptions;
+}
diff --git a/src/main/java/com/descope/model/magiclink/request/SignUpRequest.java b/src/main/java/com/descope/model/magiclink/request/SignUpRequest.java
index c5cead9b..26e29163 100644
--- a/src/main/java/com/descope/model/magiclink/request/SignUpRequest.java
+++ b/src/main/java/com/descope/model/magiclink/request/SignUpRequest.java
@@ -1,5 +1,6 @@
package com.descope.model.magiclink.request;
+import com.descope.model.magiclink.SignUpOptions;
import com.descope.model.user.User;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
@@ -18,4 +19,5 @@ public class SignUpRequest {
@JsonProperty("URI")
private String uri;
+ private SignUpOptions loginOptions;
}
diff --git a/src/main/java/com/descope/model/mgmt/IDResponse.java b/src/main/java/com/descope/model/mgmt/IDResponse.java
new file mode 100644
index 00000000..af537496
--- /dev/null
+++ b/src/main/java/com/descope/model/mgmt/IDResponse.java
@@ -0,0 +1,14 @@
+package com.descope.model.mgmt;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class IDResponse {
+ private String id;
+}
diff --git a/src/main/java/com/descope/model/mgmt/ManagementServices.java b/src/main/java/com/descope/model/mgmt/ManagementServices.java
index 8c7778e8..5203f870 100644
--- a/src/main/java/com/descope/model/mgmt/ManagementServices.java
+++ b/src/main/java/com/descope/model/mgmt/ManagementServices.java
@@ -10,6 +10,7 @@
import com.descope.sdk.mgmt.PermissionService;
import com.descope.sdk.mgmt.ProjectService;
import com.descope.sdk.mgmt.RolesService;
+import com.descope.sdk.mgmt.SsoApplicationService;
import com.descope.sdk.mgmt.SsoService;
import com.descope.sdk.mgmt.TenantService;
import com.descope.sdk.mgmt.UserService;
@@ -26,6 +27,7 @@ public class ManagementServices {
PermissionService permissionService;
RolesService rolesService;
SsoService ssoService;
+ SsoApplicationService ssoApplicationService;
FlowService flowService;
GroupService groupService;
AuditService auditService;
diff --git a/src/main/java/com/descope/model/otp/SignUpRequest.java b/src/main/java/com/descope/model/otp/SignUpRequest.java
index 661d6417..fe02a4f6 100644
--- a/src/main/java/com/descope/model/otp/SignUpRequest.java
+++ b/src/main/java/com/descope/model/otp/SignUpRequest.java
@@ -1,5 +1,6 @@
package com.descope.model.otp;
+import com.descope.model.magiclink.SignUpOptions;
import com.descope.model.user.User;
import lombok.AllArgsConstructor;
import lombok.Builder;
@@ -16,4 +17,5 @@ public class SignUpRequest {
private String email;
private String loginId;
private User user;
+ private SignUpOptions loginOptions;
}
diff --git a/src/main/java/com/descope/model/sso/AttributeMapping.java b/src/main/java/com/descope/model/sso/AttributeMapping.java
index 82f99279..50b76b10 100644
--- a/src/main/java/com/descope/model/sso/AttributeMapping.java
+++ b/src/main/java/com/descope/model/sso/AttributeMapping.java
@@ -1,17 +1,26 @@
package com.descope.model.sso;
+import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
+/**
+ * Represents a SAML mapping between Descope and IDP user attributes.
+ */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AttributeMapping {
private String name;
+ private String givenName;
+ private String middleName;
+ private String familyName;
+ private String picture;
private String email;
private String phoneNumber;
private String group;
+ private Map customAttributes;
}
diff --git a/src/main/java/com/descope/model/sso/OIDCAttributeMapping.java b/src/main/java/com/descope/model/sso/OIDCAttributeMapping.java
new file mode 100644
index 00000000..ae992ad2
--- /dev/null
+++ b/src/main/java/com/descope/model/sso/OIDCAttributeMapping.java
@@ -0,0 +1,27 @@
+package com.descope.model.sso;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * Represents a OIDC mapping between Descope and IDP user attributes.
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class OIDCAttributeMapping {
+ private String loginId;
+ private String name;
+ private String givenName;
+ private String middleName;
+ private String familyName;
+ private String email;
+ private String verifiedEmail;
+ private String username;
+ private String phoneNumber;
+ private String verifiedPhone;
+ private String picture;
+}
diff --git a/src/main/java/com/descope/model/sso/RoleMapping.java b/src/main/java/com/descope/model/sso/RoleMapping.java
index 08d85eec..c9c94fac 100644
--- a/src/main/java/com/descope/model/sso/RoleMapping.java
+++ b/src/main/java/com/descope/model/sso/RoleMapping.java
@@ -11,7 +11,6 @@
@NoArgsConstructor
@AllArgsConstructor
public class RoleMapping {
-
private List groups;
- private String role;
+ private String roleName;
}
diff --git a/src/main/java/com/descope/model/sso/SSOOIDCSettings.java b/src/main/java/com/descope/model/sso/SSOOIDCSettings.java
new file mode 100644
index 00000000..458b3e31
--- /dev/null
+++ b/src/main/java/com/descope/model/sso/SSOOIDCSettings.java
@@ -0,0 +1,31 @@
+package com.descope.model.sso;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SSOOIDCSettings {
+ private String name;
+ private String clientId;
+ private String clientSecret;
+ private String redirectUrl;
+ private String authUrl;
+ private String tokenUrl;
+ private String userDataUrl;
+ private List scope;
+ @JsonProperty("JWKsUrl")
+ private String jwksUrl;
+ private OIDCAttributeMapping userAttrMapping;
+ private Boolean manageProviderTokens;
+ private String callbackDomain;
+ private List prompt;
+ private String grantType;
+ private String issuer;
+}
diff --git a/src/main/java/com/descope/model/sso/SSOSAMLSettings.java b/src/main/java/com/descope/model/sso/SSOSAMLSettings.java
new file mode 100644
index 00000000..8538e358
--- /dev/null
+++ b/src/main/java/com/descope/model/sso/SSOSAMLSettings.java
@@ -0,0 +1,19 @@
+package com.descope.model.sso;
+
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SSOSAMLSettings {
+ private String idpUrl;
+ private String entityId;
+ private String idpCert;
+ private AttributeMapping attributeMapping;
+ private List roleMappings;
+}
diff --git a/src/main/java/com/descope/model/sso/SSOSAMLSettingsByMetadata.java b/src/main/java/com/descope/model/sso/SSOSAMLSettingsByMetadata.java
new file mode 100644
index 00000000..d643c170
--- /dev/null
+++ b/src/main/java/com/descope/model/sso/SSOSAMLSettingsByMetadata.java
@@ -0,0 +1,17 @@
+package com.descope.model.sso;
+
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SSOSAMLSettingsByMetadata {
+ private String idpMetadataUrl;
+ private AttributeMapping attributeMapping;
+ private List roleMappings;
+}
diff --git a/src/main/java/com/descope/model/sso/SSOSAMLSettingsResponse.java b/src/main/java/com/descope/model/sso/SSOSAMLSettingsResponse.java
new file mode 100644
index 00000000..745d14b8
--- /dev/null
+++ b/src/main/java/com/descope/model/sso/SSOSAMLSettingsResponse.java
@@ -0,0 +1,24 @@
+package com.descope.model.sso;
+
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SSOSAMLSettingsResponse {
+ private String idpEntityId;
+ private String idpSSOUrl;
+ private String idpCertificate;
+ private String idpMetadataUrl;
+ private String spEntityId;
+ private String spACSUrl;
+ private String spCertificate;
+ private AttributeMapping attributeMapping;
+ private List groupsMapping;
+ private String redirectUrl;
+}
diff --git a/src/main/java/com/descope/model/sso/SSOTenantSettingsResponse.java b/src/main/java/com/descope/model/sso/SSOTenantSettingsResponse.java
new file mode 100644
index 00000000..85c5dd63
--- /dev/null
+++ b/src/main/java/com/descope/model/sso/SSOTenantSettingsResponse.java
@@ -0,0 +1,17 @@
+package com.descope.model.sso;
+
+import com.descope.model.tenant.Tenant;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SSOTenantSettingsResponse {
+ private Tenant tenant;
+ private SSOSAMLSettingsResponse saml;
+ private SSOOIDCSettings oidc;
+}
diff --git a/src/main/java/com/descope/model/ssoapp/OIDCApplicationRequest.java b/src/main/java/com/descope/model/ssoapp/OIDCApplicationRequest.java
new file mode 100644
index 00000000..898645ce
--- /dev/null
+++ b/src/main/java/com/descope/model/ssoapp/OIDCApplicationRequest.java
@@ -0,0 +1,37 @@
+package com.descope.model.ssoapp;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class OIDCApplicationRequest {
+ /**
+ * Optional ID that if given must be unique per project. Will be generated if not given.
+ */
+ private String id;
+ /**
+ * The sso application's name. Must be unique per project.
+ */
+ private String name;
+ /**
+ * Optional sso application description.
+ */
+ private String description;
+ /**
+ * Optional set the sso application as enabled or disabled.
+ */
+ private Boolean enabled;
+ /**
+ * Optional sso application logo.
+ */
+ private String logo;
+ /**
+ * The URL where login page is hosted.
+ */
+ private String loginPageUrl;
+}
diff --git a/src/main/java/com/descope/model/ssoapp/SAMLApplicationRequest.java b/src/main/java/com/descope/model/ssoapp/SAMLApplicationRequest.java
new file mode 100644
index 00000000..7f18bdb0
--- /dev/null
+++ b/src/main/java/com/descope/model/ssoapp/SAMLApplicationRequest.java
@@ -0,0 +1,82 @@
+package com.descope.model.ssoapp;
+
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SAMLApplicationRequest {
+ /**
+ * Optional sso application ID. Will be auto-generated if not given. Must be unique per project.
+ */
+ private String id;
+ /**
+ * The sso application's name. Must be unique per project.
+ */
+ private String name;
+ /**
+ * Optional sso application description.
+ */
+ private String description;
+ /**
+ * Optional set the sso application as enabled or disabled.
+ */
+ private Boolean enabled;
+ /**
+ * Optional sso application logo.
+ */
+ private String logo;
+ /**
+ * The URL where login page is hosted.
+ */
+ private String loginPageUrl;
+ /**
+ * Optional determine if SP info should be automatically fetched from metadata_url
+ * or by specified it by the entity_id, acs_url, certificate parameters.
+ */
+ private Boolean useMetadataInfo;
+ /**
+ * Optional (must be set if UseMetadataInfo is True) SP metadata url which include all the SP SAML info.
+ */
+ private String metadataUrl;
+ /**
+ * Optional (must be set if UseMetadataInfo is False) SP entity id.
+ */
+ private String entityId;
+ /**
+ * Optional (must be set if UseMetadataInfo is False) SP ACS (saml callback) url.
+ */
+ private String acsUrl;
+ /**
+ * Optional (must be set if UseMetadataInfo is False)SP certificate, relevant only when SAML request must be signed.
+ */
+ private String certificate;
+ /**
+ * Optional list of Descope (IdP) attributes to SP mapping.
+ */
+ private List attributeMapping;
+ /**
+ * Optional list of Descope (IdP) roles that will be mapped to SP groups.
+ */
+ private List groupsMapping;
+ /**
+ * Optional list of urls wildcards strings represents the allowed ACS urls that will be accepted while arriving on the
+ * SAML request as SP callback urls.
+ */
+ private List acsAllowedCallbacks;
+ /**
+ * Optional define the SAML Assertion subject name type, leave empty for using Descope user-id or set to
+ * "email"/"phone".
+ */
+ private String subjectNameIdType;
+ /**
+ * Optional define the SAML Assertion subject name format, leave empty for using
+ * "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified".
+ */
+ private String subjectNameIdFormat;
+}
diff --git a/src/main/java/com/descope/model/ssoapp/SAMLIDPAttributeMappingInfo.java b/src/main/java/com/descope/model/ssoapp/SAMLIDPAttributeMappingInfo.java
new file mode 100644
index 00000000..5a5fce89
--- /dev/null
+++ b/src/main/java/com/descope/model/ssoapp/SAMLIDPAttributeMappingInfo.java
@@ -0,0 +1,16 @@
+package com.descope.model.ssoapp;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SAMLIDPAttributeMappingInfo {
+ private String name;
+ private String type;
+ private String value;
+}
diff --git a/src/main/java/com/descope/model/ssoapp/SAMLIDPGroupsMappingInfo.java b/src/main/java/com/descope/model/ssoapp/SAMLIDPGroupsMappingInfo.java
new file mode 100644
index 00000000..add86d3d
--- /dev/null
+++ b/src/main/java/com/descope/model/ssoapp/SAMLIDPGroupsMappingInfo.java
@@ -0,0 +1,19 @@
+package com.descope.model.ssoapp;
+
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SAMLIDPGroupsMappingInfo {
+ private String name;
+ private String type;
+ private String filterType;
+ private String value;
+ private List roles;
+}
diff --git a/src/main/java/com/descope/model/ssoapp/SAMLIDPRoleGroupMappingInfo.java b/src/main/java/com/descope/model/ssoapp/SAMLIDPRoleGroupMappingInfo.java
new file mode 100644
index 00000000..9e02be7c
--- /dev/null
+++ b/src/main/java/com/descope/model/ssoapp/SAMLIDPRoleGroupMappingInfo.java
@@ -0,0 +1,15 @@
+package com.descope.model.ssoapp;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SAMLIDPRoleGroupMappingInfo {
+ private String id;
+ private String name;
+}
diff --git a/src/main/java/com/descope/model/ssoapp/SSOApplication.java b/src/main/java/com/descope/model/ssoapp/SSOApplication.java
new file mode 100644
index 00000000..5935b4bf
--- /dev/null
+++ b/src/main/java/com/descope/model/ssoapp/SSOApplication.java
@@ -0,0 +1,21 @@
+package com.descope.model.ssoapp;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SSOApplication {
+ private String id;
+ private String name;
+ private String description;
+ private Boolean enabled;
+ private String logo;
+ private String appType;
+ private SSOApplicationSAMLSettings samlSettings;
+ private SSOApplicationOIDCSettings oidcSettings;
+}
diff --git a/src/main/java/com/descope/model/ssoapp/SSOApplicationOIDCSettings.java b/src/main/java/com/descope/model/ssoapp/SSOApplicationOIDCSettings.java
new file mode 100644
index 00000000..dad840a9
--- /dev/null
+++ b/src/main/java/com/descope/model/ssoapp/SSOApplicationOIDCSettings.java
@@ -0,0 +1,16 @@
+package com.descope.model.ssoapp;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SSOApplicationOIDCSettings {
+ private String loginPageUrl;
+ private String issuer;
+ private String discoveryUrl;
+}
diff --git a/src/main/java/com/descope/model/ssoapp/SSOApplicationSAMLSettings.java b/src/main/java/com/descope/model/ssoapp/SSOApplicationSAMLSettings.java
new file mode 100644
index 00000000..1b97d81f
--- /dev/null
+++ b/src/main/java/com/descope/model/ssoapp/SSOApplicationSAMLSettings.java
@@ -0,0 +1,29 @@
+package com.descope.model.ssoapp;
+
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SSOApplicationSAMLSettings {
+ private String loginPageUrl;
+ private String idpCert;
+ private Boolean useMetadataInfo;
+ private String metadataUrl;
+ private String entityId;
+ private String acsUrl;
+ private String certificate;
+ private List attributeMapping;
+ private List groupsMapping;
+ private String idpMetadataUrl;
+ private String idpEntityId;
+ private String idpSsoUrl;
+ private List acsAllowedCallbacks;
+ private String subjectNameIdType;
+ private String subjectNameIdFormat;
+}
diff --git a/src/main/java/com/descope/model/ssoapp/SSOApplications.java b/src/main/java/com/descope/model/ssoapp/SSOApplications.java
new file mode 100644
index 00000000..c560bda4
--- /dev/null
+++ b/src/main/java/com/descope/model/ssoapp/SSOApplications.java
@@ -0,0 +1,15 @@
+package com.descope.model.ssoapp;
+
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SSOApplications {
+ private List apps;
+}
diff --git a/src/main/java/com/descope/model/tenant/request/TenantSearchRequest.java b/src/main/java/com/descope/model/tenant/request/TenantSearchRequest.java
index 9dcd7a61..3334b94b 100644
--- a/src/main/java/com/descope/model/tenant/request/TenantSearchRequest.java
+++ b/src/main/java/com/descope/model/tenant/request/TenantSearchRequest.java
@@ -20,4 +20,5 @@ public class TenantSearchRequest {
Map customAttributes;
@JsonProperty("tenantSelfProvisioningDomains")
List selfProvisioningDomains;
+ String authType;
}
diff --git a/src/main/java/com/descope/model/user/request/UserRequest.java b/src/main/java/com/descope/model/user/request/UserRequest.java
index d0073f80..18f4ecf3 100644
--- a/src/main/java/com/descope/model/user/request/UserRequest.java
+++ b/src/main/java/com/descope/model/user/request/UserRequest.java
@@ -31,6 +31,7 @@ public class UserRequest {
String picture;
Boolean test;
List additionalLoginIds;
+ List ssoAppIds;
public Map toMap() {
Map m = new HashMap<>();
@@ -48,6 +49,7 @@ public Map toMap() {
addIfNotNull(m, "picture", picture);
addIfNotNull(m, "test", test);
addIfNotNull(m, "additionalLoginIds", additionalLoginIds);
+ addIfNotNull(m, "ssoAppIDs", ssoAppIds);
return m;
}
}
diff --git a/src/main/java/com/descope/model/user/response/UserResponse.java b/src/main/java/com/descope/model/user/response/UserResponse.java
index 304fae4b..e2530885 100644
--- a/src/main/java/com/descope/model/user/response/UserResponse.java
+++ b/src/main/java/com/descope/model/user/response/UserResponse.java
@@ -32,4 +32,5 @@ public class UserResponse {
Boolean TOTP;
Boolean SAML;
Map oAuth;
+ List ssoAppIds;
}
diff --git a/src/main/java/com/descope/proxy/impl/AbstractProxyImpl.java b/src/main/java/com/descope/proxy/impl/AbstractProxyImpl.java
index 3ed7d68f..278d442d 100644
--- a/src/main/java/com/descope/proxy/impl/AbstractProxyImpl.java
+++ b/src/main/java/com/descope/proxy/impl/AbstractProxyImpl.java
@@ -104,15 +104,19 @@ public R handleResponse(ClassicHttpResponse response) throws HttpException, IOEx
bs.toString(), String.valueOf(res.getCode()), bs.toString());
}
}
- R r = returnClz != null
- ? objectMapper.readValue(tee, returnClz)
- : objectMapper.readValue(tee, typeReference);
- if (log.isDebugEnabled()) {
- String resStr = bs.toString();
- log.debug(String.format("Received response %s",
- resStr.substring(0, resStr.length() > 10000 ? 10000 : resStr.length())));
+ try {
+ R r = returnClz != null
+ ? objectMapper.readValue(tee, returnClz)
+ : objectMapper.readValue(tee, typeReference);
+ if (log.isDebugEnabled()) {
+ String resStr = bs.toString();
+ log.debug(String.format("Received response %s",
+ resStr.substring(0, resStr.length() > 10000 ? 10000 : resStr.length())));
+ }
+ return r;
+ } catch (Exception e) {
+ throw ServerCommonException.parseResponseError("Error parsing response", bs.toString(), e);
}
- return r;
}
}
});
diff --git a/src/main/java/com/descope/sdk/auth/EnchantedLinkService.java b/src/main/java/com/descope/sdk/auth/EnchantedLinkService.java
index fa6ce9d1..c33ed7ed 100644
--- a/src/main/java/com/descope/sdk/auth/EnchantedLinkService.java
+++ b/src/main/java/com/descope/sdk/auth/EnchantedLinkService.java
@@ -5,6 +5,7 @@
import com.descope.model.auth.UpdateOptions;
import com.descope.model.enchantedlink.EnchantedLinkResponse;
import com.descope.model.magiclink.LoginOptions;
+import com.descope.model.magiclink.SignUpOptions;
import com.descope.model.user.User;
public interface EnchantedLinkService {
@@ -37,6 +38,19 @@ EnchantedLinkResponse signIn(
EnchantedLinkResponse signUp(String loginId, String uri, User user)
throws DescopeException;
+ /**
+ * Use to create a new user based on the given loginID either email or a phone.
+ *
+ * @param loginId - User login ID
+ * @param uri - Base URI
+ * @param user - {@link User User}
+ * @param signupOptions - optional claims and template strings
+ * @return pendingRef, linkId and masked email
+ * @throws DescopeException - error upon failure
+ */
+ EnchantedLinkResponse signUp(String loginId, String uri, User user, SignUpOptions signupOptions)
+ throws DescopeException;
+
/**
* Use to login in using loginID, if user does not exist, a new user will be created.
*
diff --git a/src/main/java/com/descope/sdk/auth/MagicLinkService.java b/src/main/java/com/descope/sdk/auth/MagicLinkService.java
index 08d90185..0029394f 100644
--- a/src/main/java/com/descope/sdk/auth/MagicLinkService.java
+++ b/src/main/java/com/descope/sdk/auth/MagicLinkService.java
@@ -5,6 +5,7 @@
import com.descope.model.auth.AuthenticationInfo;
import com.descope.model.auth.UpdateOptions;
import com.descope.model.magiclink.LoginOptions;
+import com.descope.model.magiclink.SignUpOptions;
import com.descope.model.user.User;
public interface MagicLinkService {
@@ -45,6 +46,21 @@ String signIn(
String signUp(DeliveryMethod deliveryMethod, String loginId, String uri, User user)
throws DescopeException;
+ /**
+ * Use to create a new user based on the given loginID either email, sms or whatsapp. Choose the
+ * selected delivery method for verification.
+ *
+ * @param deliveryMethod - {@link com.descope.enums.DeliveryMethod DeliveryMethod}
+ * @param loginId - User login ID
+ * @param uri - Base URI
+ * @param user - {@link User User}
+ * @param signupOptions - optional claims and template strings
+ * @return masked address where the link was sent (email, whatsapp or phone)
+ * @throws DescopeException - error upon failure
+ */
+ String signUp(DeliveryMethod deliveryMethod, String loginId, String uri, User user, SignUpOptions signupOptions)
+ throws DescopeException;
+
/**
* Verify - Use to verify a SignIn/SignUp request, based on the magic link token generated. if the
* link was generated with crossDevice, the authentication info will be nil, and should returned
diff --git a/src/main/java/com/descope/sdk/auth/OTPService.java b/src/main/java/com/descope/sdk/auth/OTPService.java
index de57cb22..5220bc7a 100644
--- a/src/main/java/com/descope/sdk/auth/OTPService.java
+++ b/src/main/java/com/descope/sdk/auth/OTPService.java
@@ -5,6 +5,7 @@
import com.descope.model.auth.AuthenticationInfo;
import com.descope.model.auth.UpdateOptions;
import com.descope.model.magiclink.LoginOptions;
+import com.descope.model.magiclink.SignUpOptions;
import com.descope.model.user.User;
public interface OTPService {
@@ -21,6 +22,20 @@ public interface OTPService {
String signIn(DeliveryMethod deliveryMethod, String loginId, LoginOptions loginOptions)
throws DescopeException;
+ /**
+ * Use to login a user based on the given loginID either email or a phone and choose the selected
+ * delivery method for verification.
+ *
+ * @param deliveryMethod - {@link com.descope.enums.DeliveryMethod DeliveryMethod}
+ * @param loginId - User login ID
+ * @param loginOptions - {@link LoginOptions LoginOptions}
+ * @param refreshToken - if doing step-up or mfa refresh token is required
+ * @return - masked address where the link was sent (email or phone)
+ * @throws DescopeException - error upon failure
+ */
+ String signIn(DeliveryMethod deliveryMethod, String loginId, LoginOptions loginOptions, String refreshToken)
+ throws DescopeException;
+
/**
* Use to create a new user based on the given loginID either email or a phone. choose the
* selected delivery method for verification.
@@ -33,6 +48,20 @@ String signIn(DeliveryMethod deliveryMethod, String loginId, LoginOptions loginO
*/
String signUp(DeliveryMethod deliveryMethod, String loginId, User user) throws DescopeException;
+ /**
+ * Use to create a new user based on the given loginID either email or a phone. choose the
+ * selected delivery method for verification.
+ *
+ * @param deliveryMethod - {@link com.descope.enums.DeliveryMethod DeliveryMethod}
+ * @param loginId - User login ID
+ * @param user - {@link User User}
+ * @param signupOptions - optional claims and template strings
+ * @return masked address where the link was sent (email, whatsapp or phone)
+ * @throws DescopeException - error upon failure
+ */
+ String signUp(DeliveryMethod deliveryMethod, String loginId, User user, SignUpOptions signupOptions)
+ throws DescopeException;
+
/**
* Use to login in using loginID, if user does not exist, a new user will be created with the
* given loginID.
diff --git a/src/main/java/com/descope/sdk/auth/SSOServiceProvider.java b/src/main/java/com/descope/sdk/auth/SSOServiceProvider.java
new file mode 100644
index 00000000..8dab7f3f
--- /dev/null
+++ b/src/main/java/com/descope/sdk/auth/SSOServiceProvider.java
@@ -0,0 +1,44 @@
+package com.descope.sdk.auth;
+
+import com.descope.exception.DescopeException;
+import com.descope.model.auth.AuthenticationInfo;
+import com.descope.model.magiclink.LoginOptions;
+
+public interface SSOServiceProvider {
+ /**
+ * Start will initiate an SSO login flow.
+ *
+ * @param tenant - tenant
+ * @param redirectUrl - URL to redirect user to overriding configuration
+ * @param prompt - Prompt to the user overriding configuration
+ * @param loginOptions - {@link LoginOptions loginOptions}
+ * @return will be the redirect URL that needs to return to client
+ * @throws DescopeException - error upon failure
+ */
+ String start(String tenant, String redirectUrl, String prompt, LoginOptions loginOptions) throws DescopeException;
+
+ /**
+ * Start will initiate an SSO login flow.
+ *
+ * @param tenant - tenant
+ * @param redirectUrl - URL to redirect user to overriding configuration
+ * @param prompt - Prompt to the user overriding configuration
+ * @param loginOptions - {@link LoginOptions loginOptions}
+ * @param refreshToken - if we are doing step-up or MFA, existing refresh token is required
+ * @return will be the redirect URL that needs to return to client
+ * @throws DescopeException - error upon failure
+ */
+ String start(String tenant, String redirectUrl, String prompt, LoginOptions loginOptions, String refreshToken)
+ throws DescopeException;
+
+ /**
+ * ExchangeToken - Finalize SAML/OIDC SSO authentication.
+ *
+ * @param code - Code to be validated
+ * @return Authentication info
+ * @throws DescopeException - error upon failure
+ */
+
+ AuthenticationInfo exchangeToken(String code) throws DescopeException;
+
+}
diff --git a/src/main/java/com/descope/sdk/auth/impl/AuthenticationServiceBuilder.java b/src/main/java/com/descope/sdk/auth/impl/AuthenticationServiceBuilder.java
index cc4d9868..f07b6eb1 100644
--- a/src/main/java/com/descope/sdk/auth/impl/AuthenticationServiceBuilder.java
+++ b/src/main/java/com/descope/sdk/auth/impl/AuthenticationServiceBuilder.java
@@ -11,6 +11,7 @@ public static AuthenticationServices buildServices(Client client) {
.authService(new AuthenticationServiceImpl(client))
.otpService(new OTPServiceImpl(client))
.samlService(new SAMLServiceImpl(client))
+ .ssoServiceProvider(new SSOServiceProviderImpl(client))
.totpService(new TOTPServiceImpl(client))
.oauthService(new OAuthServiceImpl(client))
.passwordService(new PasswordServiceImpl(client))
diff --git a/src/main/java/com/descope/sdk/auth/impl/EnchantedLinkServiceImpl.java b/src/main/java/com/descope/sdk/auth/impl/EnchantedLinkServiceImpl.java
index 2d2b8220..4fc8128f 100644
--- a/src/main/java/com/descope/sdk/auth/impl/EnchantedLinkServiceImpl.java
+++ b/src/main/java/com/descope/sdk/auth/impl/EnchantedLinkServiceImpl.java
@@ -19,6 +19,7 @@
import com.descope.model.enchantedlink.EnchantedLinkSessionBody;
import com.descope.model.jwt.response.JWTResponse;
import com.descope.model.magiclink.LoginOptions;
+import com.descope.model.magiclink.SignUpOptions;
import com.descope.model.magiclink.request.SignInRequest;
import com.descope.model.magiclink.request.SignUpRequest;
import com.descope.model.magiclink.request.UpdateEmailRequest;
@@ -59,6 +60,12 @@ public EnchantedLinkResponse signIn(String loginId, String uri, String token, Lo
@Override
public EnchantedLinkResponse signUp(String loginId, String uri, User user)
throws DescopeException {
+ return signUp(loginId, uri, user, null);
+ }
+
+ @Override
+ public EnchantedLinkResponse signUp(String loginId, String uri, User user, SignUpOptions signupOptions)
+ throws DescopeException {
if (user == null) {
user = new User();
}
@@ -68,6 +75,9 @@ public EnchantedLinkResponse signUp(String loginId, String uri, User user)
if (StringUtils.isBlank(user.getEmail())) {
user.setEmail(loginId);
}
+ if (signupOptions != null) {
+ signUpRequestBuilder.loginOptions(signupOptions);
+ }
SignUpRequest signUpRequest = signUpRequestBuilder.user(user).build();
ApiProxy apiProxy = getApiProxy();
return apiProxy.post(enchantedLinkSignUpURL, signUpRequest, EnchantedLinkResponse.class);
diff --git a/src/main/java/com/descope/sdk/auth/impl/MagicLinkServiceImpl.java b/src/main/java/com/descope/sdk/auth/impl/MagicLinkServiceImpl.java
index c37a28fb..802e79dc 100644
--- a/src/main/java/com/descope/sdk/auth/impl/MagicLinkServiceImpl.java
+++ b/src/main/java/com/descope/sdk/auth/impl/MagicLinkServiceImpl.java
@@ -19,6 +19,7 @@
import com.descope.model.client.Client;
import com.descope.model.jwt.response.JWTResponse;
import com.descope.model.magiclink.LoginOptions;
+import com.descope.model.magiclink.SignUpOptions;
import com.descope.model.magiclink.request.SignInRequest;
import com.descope.model.magiclink.request.SignUpRequest;
import com.descope.model.magiclink.request.UpdateEmailRequest;
@@ -67,8 +68,13 @@ public String signIn(
}
@Override
- public String signUp(DeliveryMethod deliveryMethod, String loginId, String uri, User user)
- throws DescopeException {
+ public String signUp(DeliveryMethod deliveryMethod, String loginId, String uri, User user) {
+ return signUp(deliveryMethod, loginId, uri, user, null);
+ }
+
+ @Override
+ public String signUp(DeliveryMethod deliveryMethod, String loginId, String uri, User user,
+ SignUpOptions signupOptions) throws DescopeException {
if (user == null) {
user = new User();
}
@@ -81,7 +87,9 @@ public String signUp(DeliveryMethod deliveryMethod, String loginId, String uri,
if (EMAIL.equals(deliveryMethod)) {
signUpRequestBuilder.email(user.getEmail());
}
-
+ if (signupOptions != null) {
+ signUpRequestBuilder.loginOptions(signupOptions);
+ }
SignUpRequest signUpRequest = signUpRequestBuilder.user(user).build();
ApiProxy apiProxy = getApiProxy();
Masked masked = apiProxy.post(magicLinkSignUpURL, signUpRequest, maskedClass);
diff --git a/src/main/java/com/descope/sdk/auth/impl/OTPServiceImpl.java b/src/main/java/com/descope/sdk/auth/impl/OTPServiceImpl.java
index cf2b7024..e2963e9d 100644
--- a/src/main/java/com/descope/sdk/auth/impl/OTPServiceImpl.java
+++ b/src/main/java/com/descope/sdk/auth/impl/OTPServiceImpl.java
@@ -20,6 +20,7 @@
import com.descope.model.client.Client;
import com.descope.model.jwt.response.JWTResponse;
import com.descope.model.magiclink.LoginOptions;
+import com.descope.model.magiclink.SignUpOptions;
import com.descope.model.magiclink.response.Masked;
import com.descope.model.otp.AuthenticationVerifyRequestBody;
import com.descope.model.otp.SignInRequest;
@@ -40,7 +41,12 @@ class OTPServiceImpl extends AuthenticationServiceImpl implements OTPService {
}
@Override
- public String signIn(DeliveryMethod deliveryMethod, String loginId, LoginOptions loginOptions)
+ public String signIn(DeliveryMethod deliveryMethod, String loginId, LoginOptions loginOptions) {
+ return signIn(deliveryMethod, loginId, loginOptions, null);
+ }
+
+ @Override
+ public String signIn(DeliveryMethod deliveryMethod, String loginId, LoginOptions loginOptions, String refreshToken)
throws DescopeException {
if (StringUtils.isBlank(loginId)) {
throw ServerCommonException.invalidArgument("Login ID");
@@ -50,8 +56,10 @@ public String signIn(DeliveryMethod deliveryMethod, String loginId, LoginOptions
URI otpSignInURL = composeSignInURL(deliveryMethod);
SignInRequest signInRequest = new SignInRequest(loginId, loginOptions);
if (JwtUtils.isJWTRequired(loginOptions)) {
- String pwd = ""; // getValidRefreshToken(request);
- apiProxy = getApiProxy(pwd);
+ if (StringUtils.isBlank(refreshToken)) {
+ throw ServerCommonException.invalidArgument("refreshToken");
+ }
+ apiProxy = getApiProxy(refreshToken);
} else {
apiProxy = getApiProxy();
}
@@ -60,19 +68,25 @@ public String signIn(DeliveryMethod deliveryMethod, String loginId, LoginOptions
}
@Override
- public String signUp(DeliveryMethod deliveryMethod, String loginId, User user)
+ public String signUp(DeliveryMethod deliveryMethod, String loginId, User user) {
+ return signUp(deliveryMethod, loginId, user, null);
+ }
+
+ @Override
+ public String signUp(DeliveryMethod deliveryMethod, String loginId, User user, SignUpOptions signUpOptions)
throws DescopeException {
if (user == null) {
user = new User();
}
verifyDeliveryMethod(deliveryMethod, loginId, user);
- Class extends Masked> maskedClass = getMaskedValue(deliveryMethod);
- URI otpSignUpURL = composeSignUpURI(deliveryMethod);
-
SignUpRequest signUpRequest = newSignUpRequest(deliveryMethod, user);
signUpRequest.setLoginId(loginId);
signUpRequest.setUser(user);
-
+ if (signUpOptions != null) {
+ signUpRequest.setLoginOptions(signUpOptions);
+ }
+ Class extends Masked> maskedClass = getMaskedValue(deliveryMethod);
+ URI otpSignUpURL = composeSignUpURI(deliveryMethod);
ApiProxy apiProxy = getApiProxy();
Masked masked = apiProxy.post(otpSignUpURL, signUpRequest, maskedClass);
return masked.getMasked();
diff --git a/src/main/java/com/descope/sdk/auth/impl/SSOServiceProviderImpl.java b/src/main/java/com/descope/sdk/auth/impl/SSOServiceProviderImpl.java
new file mode 100644
index 00000000..6f705a13
--- /dev/null
+++ b/src/main/java/com/descope/sdk/auth/impl/SSOServiceProviderImpl.java
@@ -0,0 +1,59 @@
+package com.descope.sdk.auth.impl;
+
+import static com.descope.literals.Routes.AuthEndPoints.COMPOSE_SSO_START_LINK;
+import static com.descope.literals.Routes.AuthEndPoints.EXCHANGE_SSO_LINK;
+import static com.descope.utils.CollectionUtils.mapOf;
+
+import com.descope.exception.DescopeException;
+import com.descope.exception.ServerCommonException;
+import com.descope.model.auth.AuthenticationInfo;
+import com.descope.model.auth.OAuthResponse;
+import com.descope.model.client.Client;
+import com.descope.model.magiclink.LoginOptions;
+import com.descope.proxy.ApiProxy;
+import com.descope.sdk.auth.SSOServiceProvider;
+import com.descope.utils.JwtUtils;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+
+class SSOServiceProviderImpl extends AuthenticationServiceImpl implements SSOServiceProvider {
+
+ SSOServiceProviderImpl(Client client) {
+ super(client);
+ }
+
+ @Override
+ public String start(String tenant, String redirectUrl, String prompt, LoginOptions loginOptions) {
+ return start(tenant, redirectUrl, prompt, loginOptions, null);
+ }
+
+ @Override
+ public String start(String tenant, String redirectUrl, String prompt, LoginOptions loginOptions, String refreshToken)
+ throws DescopeException {
+ if (StringUtils.isBlank(tenant)) {
+ throw ServerCommonException.invalidArgument("Tenant");
+ }
+ Map request = mapOf("tenant", tenant);
+ if (StringUtils.isNotBlank(redirectUrl)) {
+ request.put("redirectURL", redirectUrl);
+ }
+ if (StringUtils.isNotBlank(prompt)) {
+ request.put("prompt", prompt);
+ }
+ ApiProxy apiProxy;
+ if (JwtUtils.isJWTRequired(loginOptions)) {
+ if (StringUtils.isBlank(refreshToken)) {
+ throw ServerCommonException.invalidArgument("refreshToken");
+ }
+ apiProxy = getApiProxy(refreshToken);
+ } else {
+ apiProxy = getApiProxy();
+ }
+ return apiProxy.post(getQueryParamUri(COMPOSE_SSO_START_LINK, request), loginOptions, OAuthResponse.class).getUrl();
+ }
+
+ @Override
+ public AuthenticationInfo exchangeToken(String code) throws DescopeException {
+ return exchangeToken(code, getUri(EXCHANGE_SSO_LINK));
+ }
+}
diff --git a/src/main/java/com/descope/sdk/mgmt/SsoApplicationService.java b/src/main/java/com/descope/sdk/mgmt/SsoApplicationService.java
new file mode 100644
index 00000000..821be464
--- /dev/null
+++ b/src/main/java/com/descope/sdk/mgmt/SsoApplicationService.java
@@ -0,0 +1,72 @@
+package com.descope.sdk.mgmt;
+
+import com.descope.exception.DescopeException;
+import com.descope.model.ssoapp.OIDCApplicationRequest;
+import com.descope.model.ssoapp.SAMLApplicationRequest;
+import com.descope.model.ssoapp.SSOApplication;
+import java.util.List;
+
+public interface SsoApplicationService {
+
+ /**
+ * Create a new OIDC SSO application with the given name.
+ *
+ * @param appRequest the OIDC application details
+ * @return the new ID of the application
+ * @throws DescopeException If error, a subtype of this exception will be thrown
+ */
+ String createOIDCApplication(OIDCApplicationRequest appRequest) throws DescopeException;
+
+ /**
+ * Create a new SAML SSO application with the given name.
+ *
+ * @param appRequest the SAML application details
+ * @return the new ID of the application
+ * @throws DescopeException If error, a subtype of this exception will be thrown
+ */
+ String createSAMLApplication(SAMLApplicationRequest appRequest) throws DescopeException;
+
+ /**
+ * Update an existing OIDC sso application.
+ * IMPORTANT: All parameters are required and will override whatever value is currently
+ *
+ * @param appRequest the application details.
+ * @throws DescopeException If error, a subtype of this exception will be thrown
+ */
+ void updateOIDCApplication(OIDCApplicationRequest appRequest) throws DescopeException;
+
+ /**
+ * Update an existing SAML sso application.
+ * IMPORTANT: All parameters are required and will override whatever value is currently
+ *
+ * @param appRequest the application details.
+ * @throws DescopeException If error, a subtype of this exception will be thrown
+ */
+ void updateSAMLApplication(SAMLApplicationRequest appRequest) throws DescopeException;
+
+ /**
+ * Delete an existing sso application.
+ * IMPORTANT: This action is irreversible. Use carefully.
+ *
+ * @param id application ID to delete
+ * @throws DescopeException If error, a subtype of this exception will be thrown
+ */
+ void delete(String id) throws DescopeException;
+
+ /**
+ * Load project sso application by id.
+ *
+ * @param id ID of application to load
+ * @return {@link SSOApplication} details
+ * @throws DescopeException If error, a subtype of this exception will be thrown
+ */
+ SSOApplication load(String id) throws DescopeException;
+
+ /**
+ * Load all project sso applications.
+ *
+ * @return {@link SSOApplication} details
+ * @throws DescopeException If error, a subtype of this exception will be thrown
+ */
+ List loadAll() throws DescopeException;
+}
diff --git a/src/main/java/com/descope/sdk/mgmt/SsoService.java b/src/main/java/com/descope/sdk/mgmt/SsoService.java
index de167559..8b975d18 100644
--- a/src/main/java/com/descope/sdk/mgmt/SsoService.java
+++ b/src/main/java/com/descope/sdk/mgmt/SsoService.java
@@ -3,19 +3,77 @@
import com.descope.exception.DescopeException;
import com.descope.model.sso.AttributeMapping;
import com.descope.model.sso.RoleMapping;
+import com.descope.model.sso.SSOOIDCSettings;
+import com.descope.model.sso.SSOSAMLSettings;
+import com.descope.model.sso.SSOSAMLSettingsByMetadata;
import com.descope.model.sso.SSOSettingsResponse;
+import com.descope.model.sso.SSOTenantSettingsResponse;
import java.util.List;
public interface SsoService {
+ /**
+ * Load all tenant SSO setting.
+ *
+ * @param tenantId the tenant ID we are loading settings for
+ * @return {@link SSOTenantSettingsResponse} all SSO settings for the tenant
+ * @throws DescopeException If error, a subtype of this exception will be thrown
+ */
+ SSOTenantSettingsResponse loadSettings(String tenantId) throws DescopeException;
+
+ /**
+ * Configure SSO SAML settings for a tenant manually.
+ *
+ * @param tenantId required tenant ID
+ * @param settings required settings with all fields set
+ * @param redirectUrl optional. If absent, must be specified when starting an SSO authentication via the request
+ * @param domains optional and is used to map users to this tenant when authenticating via SSO.
+ * @throws DescopeException If error, a subtype of this exception will be thrown
+ */
+ void configureSAMLSettings(String tenantId, SSOSAMLSettings settings, String redirectUrl, List domains)
+ throws DescopeException;
+
+ /**
+ * Configure SSO SAML settings for a tenant by fetching them from an IDP metadata URL.
+ *
+ * @param tenantId required tenant ID
+ * @param settings required settings with all fields set
+ * @param redirectUrl optional. If absent, must be specified when starting an SSO authentication via the request
+ * @param domains optional and is used to map users to this tenant when authenticating via SSO.
+ * @throws DescopeException If error, a subtype of this exception will be thrown
+ */
+ void configureSAMLSettingsByMetadata(String tenantId, SSOSAMLSettingsByMetadata settings, String redirectUrl,
+ List domains) throws DescopeException;
+
+ /**
+ * Configure SSO OIDC settings for a tenant manually.
+ *
+ * @param tenantId required tenant ID
+ * @param settings required settings
+ * @param domains optional and is used to map users to this tenant when authenticating via SSO.
+ * @throws DescopeException If error, a subtype of this exception will be thrown
+ */
+ void configureOIDCSettings(String tenantId, SSOOIDCSettings settings, List domains) throws DescopeException;
+
+ /**
+ * Delete the SSO settings for the given tenant.
+ *
+ * @param tenantId required tenant ID
+ * @throws DescopeException If error, a subtype of this exception will be thrown
+ */
+ void deleteSettings(String tenantId) throws DescopeException;
+
+ @Deprecated
SSOSettingsResponse getSettings(String tenantID) throws DescopeException;
- void deleteSettings(String tenantID) throws DescopeException;
+ @Deprecated
void configureSettings(String tenantID, String idpURL, String idpCert, String entityID,
String redirectURL, List domains) throws DescopeException;
+ @Deprecated
void configureMetadata(String tenantID, String idpMetadataURL) throws DescopeException;
+ @Deprecated
void configureMapping(String tenantID, List roleMapping,
AttributeMapping attributeMapping) throws DescopeException;
}
diff --git a/src/main/java/com/descope/sdk/mgmt/UserService.java b/src/main/java/com/descope/sdk/mgmt/UserService.java
index 5888341c..7fd5efba 100644
--- a/src/main/java/com/descope/sdk/mgmt/UserService.java
+++ b/src/main/java/com/descope/sdk/mgmt/UserService.java
@@ -317,6 +317,39 @@ UserResponseDetails updateCustomAttributes(String loginId, String key, Object va
*/
UserResponseDetails removeRoles(String loginId, List roles) throws DescopeException;
+ /**
+ * Set (associate) SSO applications for a user.
+ *
+ * @param loginId The loginID is required.
+ * @param ssoAppIds List of SSO apps to associate
+ * @return {@link UserResponseDetails UserResponseDetails}
+ * @throws DescopeException If there occurs any exception, a subtype of this exception will be
+ * thrown.
+ */
+ UserResponseDetails addSsoApps(String loginId, List ssoAppIds) throws DescopeException;
+
+ /**
+ * Remove SSO application association from a user.
+ *
+ * @param loginId The loginID is required.
+ * @param ssoAppIds List of SSO apps to associate
+ * @return {@link UserResponseDetails UserResponseDetails}
+ * @throws DescopeException If there occurs any exception, a subtype of this exception will be
+ * thrown.
+ */
+ UserResponseDetails removeSsoApps(String loginId, List ssoAppIds) throws DescopeException;
+
+ /**
+ * Associate SSO applications for a user.
+ *
+ * @param loginId The loginID is required.
+ * @param ssoAppIds List of SSO apps to associate
+ * @return {@link UserResponseDetails UserResponseDetails}
+ * @throws DescopeException If there occurs any exception, a subtype of this exception will be
+ * thrown.
+ */
+ UserResponseDetails setSsoApps(String loginId, List ssoAppIds) throws DescopeException;
+
/**
* Add a tenant association for an existing user.
*
diff --git a/src/main/java/com/descope/sdk/mgmt/impl/ManagementServiceBuilder.java b/src/main/java/com/descope/sdk/mgmt/impl/ManagementServiceBuilder.java
index cdab40d8..098cb870 100644
--- a/src/main/java/com/descope/sdk/mgmt/impl/ManagementServiceBuilder.java
+++ b/src/main/java/com/descope/sdk/mgmt/impl/ManagementServiceBuilder.java
@@ -21,6 +21,7 @@ public static ManagementServices buildServices(Client client) {
.authzService(new AuthzServiceImpl(client))
.projectService(new ProjectServiceImpl(client))
.passwordSettingsService(new PasswordSettingsServiceImpl(client))
+ .ssoApplicationService(new SsoApplicationServiceImpl(client))
.build();
}
}
diff --git a/src/main/java/com/descope/sdk/mgmt/impl/SsoApplicationServiceImpl.java b/src/main/java/com/descope/sdk/mgmt/impl/SsoApplicationServiceImpl.java
new file mode 100644
index 00000000..a2496a37
--- /dev/null
+++ b/src/main/java/com/descope/sdk/mgmt/impl/SsoApplicationServiceImpl.java
@@ -0,0 +1,110 @@
+package com.descope.sdk.mgmt.impl;
+
+import static com.descope.literals.Routes.ManagementEndPoints.SSO_APPLICATION_DELETE_LINK;
+import static com.descope.literals.Routes.ManagementEndPoints.SSO_APPLICATION_LOAD_ALL_LINK;
+import static com.descope.literals.Routes.ManagementEndPoints.SSO_APPLICATION_LOAD_LINK;
+import static com.descope.literals.Routes.ManagementEndPoints.SSO_APPLICATION_OIDC_CREATE_LINK;
+import static com.descope.literals.Routes.ManagementEndPoints.SSO_APPLICATION_OIDC_UPDATE_LINK;
+import static com.descope.literals.Routes.ManagementEndPoints.SSO_APPLICATION_SAML_CREATE_LINK;
+import static com.descope.literals.Routes.ManagementEndPoints.SSO_APPLICATION_SAML_UPDATE_LINK;
+import static com.descope.utils.CollectionUtils.mapOf;
+
+import com.descope.exception.DescopeException;
+import com.descope.exception.ServerCommonException;
+import com.descope.model.client.Client;
+import com.descope.model.mgmt.IDResponse;
+import com.descope.model.ssoapp.OIDCApplicationRequest;
+import com.descope.model.ssoapp.SAMLApplicationRequest;
+import com.descope.model.ssoapp.SSOApplication;
+import com.descope.model.ssoapp.SSOApplications;
+import com.descope.proxy.ApiProxy;
+import com.descope.sdk.mgmt.SsoApplicationService;
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+
+class SsoApplicationServiceImpl extends ManagementsBase implements SsoApplicationService {
+ SsoApplicationServiceImpl(Client client) {
+ super(client);
+ }
+
+ @Override
+ public String createOIDCApplication(OIDCApplicationRequest appRequest) throws DescopeException {
+ if (appRequest == null) {
+ throw ServerCommonException.invalidArgument("appRequest");
+ }
+ if (StringUtils.isBlank(appRequest.getName())) {
+ throw ServerCommonException.invalidArgument("appRequest.Name");
+ }
+ ApiProxy apiProxy = getApiProxy();
+ IDResponse id = apiProxy.post(getUri(SSO_APPLICATION_OIDC_CREATE_LINK), appRequest, IDResponse.class);
+ return id.getId();
+ }
+
+ @Override
+ public String createSAMLApplication(SAMLApplicationRequest appRequest) throws DescopeException {
+ if (appRequest == null) {
+ throw ServerCommonException.invalidArgument("appRequest");
+ }
+ if (StringUtils.isBlank(appRequest.getName())) {
+ throw ServerCommonException.invalidArgument("appRequest.Name");
+ }
+ ApiProxy apiProxy = getApiProxy();
+ IDResponse id = apiProxy.post(getUri(SSO_APPLICATION_SAML_CREATE_LINK), appRequest, IDResponse.class);
+ return id.getId();
+ }
+
+ @Override
+ public void updateOIDCApplication(OIDCApplicationRequest appRequest) throws DescopeException {
+ if (appRequest == null) {
+ throw ServerCommonException.invalidArgument("appRequest");
+ }
+ if (StringUtils.isBlank(appRequest.getId())) {
+ throw ServerCommonException.invalidArgument("appRequest.Id");
+ }
+ if (StringUtils.isBlank(appRequest.getName())) {
+ throw ServerCommonException.invalidArgument("appRequest.Name");
+ }
+ ApiProxy apiProxy = getApiProxy();
+ apiProxy.post(getUri(SSO_APPLICATION_OIDC_UPDATE_LINK), appRequest, Void.class);
+ }
+
+ @Override
+ public void updateSAMLApplication(SAMLApplicationRequest appRequest) throws DescopeException {
+ if (appRequest == null) {
+ throw ServerCommonException.invalidArgument("appRequest");
+ }
+ if (StringUtils.isBlank(appRequest.getId())) {
+ throw ServerCommonException.invalidArgument("appRequest.Id");
+ }
+ if (StringUtils.isBlank(appRequest.getName())) {
+ throw ServerCommonException.invalidArgument("appRequest.Name");
+ }
+ ApiProxy apiProxy = getApiProxy();
+ apiProxy.post(getUri(SSO_APPLICATION_SAML_UPDATE_LINK), appRequest, Void.class);
+ }
+
+ @Override
+ public void delete(String id) throws DescopeException {
+ if (StringUtils.isBlank(id)) {
+ throw ServerCommonException.invalidArgument("id");
+ }
+ ApiProxy apiProxy = getApiProxy();
+ apiProxy.post(getUri(SSO_APPLICATION_DELETE_LINK), mapOf("id", id), Void.class);
+ }
+
+ @Override
+ public SSOApplication load(String id) throws DescopeException {
+ if (StringUtils.isBlank(id)) {
+ throw ServerCommonException.invalidArgument("id");
+ }
+ ApiProxy apiProxy = getApiProxy();
+ return apiProxy.get(getQueryParamUri(SSO_APPLICATION_LOAD_LINK, mapOf("id", id)), SSOApplication.class);
+ }
+
+ @Override
+ public List loadAll() throws DescopeException {
+ ApiProxy apiProxy = getApiProxy();
+ SSOApplications apps = apiProxy.get(getUri(SSO_APPLICATION_LOAD_ALL_LINK), SSOApplications.class);
+ return apps.getApps();
+ }
+}
diff --git a/src/main/java/com/descope/sdk/mgmt/impl/SsoServiceImpl.java b/src/main/java/com/descope/sdk/mgmt/impl/SsoServiceImpl.java
index 2a093f90..67c09f66 100644
--- a/src/main/java/com/descope/sdk/mgmt/impl/SsoServiceImpl.java
+++ b/src/main/java/com/descope/sdk/mgmt/impl/SsoServiceImpl.java
@@ -2,9 +2,13 @@
import static com.descope.literals.Routes.ManagementEndPoints.SSO_CONFIGURE_MAPPING_LINK;
import static com.descope.literals.Routes.ManagementEndPoints.SSO_CONFIGURE_METADATA_LINK;
+import static com.descope.literals.Routes.ManagementEndPoints.SSO_CONFIGURE_OIDC_SETTINGS_LINK;
+import static com.descope.literals.Routes.ManagementEndPoints.SSO_CONFIGURE_SAML_SETTINGS_BY_MD_LINK;
+import static com.descope.literals.Routes.ManagementEndPoints.SSO_CONFIGURE_SAML_SETTINGS_LINK;
import static com.descope.literals.Routes.ManagementEndPoints.SSO_CONFIGURE_SETTINGS_LINK;
import static com.descope.literals.Routes.ManagementEndPoints.SSO_DELETE_SETTINGS_LINK;
import static com.descope.literals.Routes.ManagementEndPoints.SSO_GET_SETTINGS_LINK;
+import static com.descope.literals.Routes.ManagementEndPoints.SSO_GET_SETTINGS_V2_LINK;
import static com.descope.utils.CollectionUtils.mapOf;
import com.descope.exception.DescopeException;
@@ -12,7 +16,11 @@
import com.descope.model.client.Client;
import com.descope.model.sso.AttributeMapping;
import com.descope.model.sso.RoleMapping;
+import com.descope.model.sso.SSOOIDCSettings;
+import com.descope.model.sso.SSOSAMLSettings;
+import com.descope.model.sso.SSOSAMLSettingsByMetadata;
import com.descope.model.sso.SSOSettingsResponse;
+import com.descope.model.sso.SSOTenantSettingsResponse;
import com.descope.proxy.ApiProxy;
import com.descope.sdk.mgmt.SsoService;
import java.util.List;
@@ -25,23 +33,91 @@ class SsoServiceImpl extends ManagementsBase implements SsoService {
}
@Override
- public SSOSettingsResponse getSettings(String tenantID) throws DescopeException {
- if (StringUtils.isBlank(tenantID)) {
+ public SSOTenantSettingsResponse loadSettings(String tenantId) throws DescopeException {
+ if (StringUtils.isBlank(tenantId)) {
throw ServerCommonException.invalidArgument("TenantId");
}
- Map params = mapOf("tenantId", tenantID);
+ Map params = mapOf("tenantId", tenantId);
ApiProxy apiProxy = getApiProxy();
- return apiProxy.get(getQueryParamUri(SSO_GET_SETTINGS_LINK, params), SSOSettingsResponse.class);
+ return apiProxy.get(getQueryParamUri(SSO_GET_SETTINGS_V2_LINK, params), SSOTenantSettingsResponse.class);
}
@Override
- public void deleteSettings(String tenantID) throws DescopeException {
+ public void configureSAMLSettings(String tenantId, SSOSAMLSettings settings, String redirectUrl, List domains)
+ throws DescopeException {
+ if (StringUtils.isBlank(tenantId)) {
+ throw ServerCommonException.invalidArgument("TenantId");
+ }
+ if (settings == null) {
+ throw ServerCommonException.invalidArgument("settings");
+ }
+ if (StringUtils.isBlank(settings.getIdpUrl())) {
+ throw ServerCommonException.invalidArgument("idpUrl");
+ }
+ if (StringUtils.isBlank(settings.getIdpCert())) {
+ throw ServerCommonException.invalidArgument("idpCert");
+ }
+ if (StringUtils.isBlank(settings.getEntityId())) {
+ throw ServerCommonException.invalidArgument("entityId");
+ }
+ Map req = mapOf("tenantId", tenantId, "redirectUrl", redirectUrl, "domains", domains,
+ "settings", settings);
+ ApiProxy apiProxy = getApiProxy();
+ apiProxy.post(getUri(SSO_CONFIGURE_SAML_SETTINGS_LINK), req, Void.class);
+ }
+
+ @Override
+ public void configureSAMLSettingsByMetadata(String tenantId, SSOSAMLSettingsByMetadata settings, String redirectUrl,
+ List domains) throws DescopeException {
+ if (StringUtils.isBlank(tenantId)) {
+ throw ServerCommonException.invalidArgument("TenantId");
+ }
+ if (settings == null) {
+ throw ServerCommonException.invalidArgument("settings");
+ }
+ if (StringUtils.isBlank(settings.getIdpMetadataUrl())) {
+ throw ServerCommonException.invalidArgument("idpMetadataURL");
+ }
+ Map req = mapOf("tenantId", tenantId, "redirectUrl", redirectUrl, "domains", domains,
+ "settings", settings);
+ ApiProxy apiProxy = getApiProxy();
+ apiProxy.post(getUri(SSO_CONFIGURE_SAML_SETTINGS_BY_MD_LINK), req, Void.class);
+ }
+
+ @Override
+ public void configureOIDCSettings(String tenantId, SSOOIDCSettings settings, List domains)
+ throws DescopeException {
+ if (StringUtils.isBlank(tenantId)) {
+ throw ServerCommonException.invalidArgument("TenantId");
+ }
+ if (settings == null) {
+ throw ServerCommonException.invalidArgument("settings");
+ }
+ Map req = mapOf("tenantId", tenantId, "domains", domains, "settings", settings);
+ ApiProxy apiProxy = getApiProxy();
+ apiProxy.post(getUri(SSO_CONFIGURE_OIDC_SETTINGS_LINK), req, Void.class);
+ }
+
+ @Override
+ public void deleteSettings(String tenantId) throws DescopeException {
+ if (StringUtils.isBlank(tenantId)) {
+ throw ServerCommonException.invalidArgument("TenantId");
+ }
+ Map request = mapOf("tenantId", tenantId);
+ ApiProxy apiProxy = getApiProxy();
+ apiProxy.delete(getQueryParamUri(SSO_DELETE_SETTINGS_LINK, request), null, Void.class);
+ }
+
+ // DEPRECATED METHODS
+
+ @Override
+ public SSOSettingsResponse getSettings(String tenantID) throws DescopeException {
if (StringUtils.isBlank(tenantID)) {
throw ServerCommonException.invalidArgument("TenantId");
}
- Map request = mapOf("tenantId", tenantID);
+ Map params = mapOf("tenantId", tenantID);
ApiProxy apiProxy = getApiProxy();
- apiProxy.delete(getUri(SSO_DELETE_SETTINGS_LINK), request, Void.class);
+ return apiProxy.get(getQueryParamUri(SSO_GET_SETTINGS_LINK, params), SSOSettingsResponse.class);
}
@Override
diff --git a/src/main/java/com/descope/sdk/mgmt/impl/UserServiceImpl.java b/src/main/java/com/descope/sdk/mgmt/impl/UserServiceImpl.java
index 58df910b..1712f4de 100644
--- a/src/main/java/com/descope/sdk/mgmt/impl/UserServiceImpl.java
+++ b/src/main/java/com/descope/sdk/mgmt/impl/UserServiceImpl.java
@@ -16,6 +16,7 @@
import static com.descope.literals.Routes.ManagementEndPoints.UPDATE_USER_LOGIN_ID_LINK;
import static com.descope.literals.Routes.ManagementEndPoints.UPDATE_USER_NAME_LINK;
import static com.descope.literals.Routes.ManagementEndPoints.USER_ADD_ROLES_LINK;
+import static com.descope.literals.Routes.ManagementEndPoints.USER_ADD_SSO_APPS_LINK;
import static com.descope.literals.Routes.ManagementEndPoints.USER_ADD_TENANT_LINK;
import static com.descope.literals.Routes.ManagementEndPoints.USER_CREATE_EMBEDDED_LINK;
import static com.descope.literals.Routes.ManagementEndPoints.USER_EXPIRE_PASSWORD_LINK;
@@ -25,6 +26,7 @@
import static com.descope.literals.Routes.ManagementEndPoints.USER_SEARCH_ALL_LINK;
import static com.descope.literals.Routes.ManagementEndPoints.USER_SET_PASSWORD_LINK;
import static com.descope.literals.Routes.ManagementEndPoints.USER_SET_ROLES_LINK;
+import static com.descope.literals.Routes.ManagementEndPoints.USER_SET_SSO_APPS_LINK;
import static com.descope.literals.Routes.ManagementEndPoints.USER_UPDATE_EMAIL_LINK;
import static com.descope.literals.Routes.ManagementEndPoints.USER_UPDATE_PHONE_LINK;
import static com.descope.literals.Routes.ManagementEndPoints.USER_UPDATE_STATUS_LINK;
@@ -378,6 +380,36 @@ public UserResponseDetails removeRoles(String loginId, List roles)
return apiProxy.post(removeRolesUri, request, UserResponseDetails.class);
}
+ @Override
+ public UserResponseDetails addSsoApps(String loginId, List ssoAppIds) throws DescopeException {
+ if (StringUtils.isBlank(loginId)) {
+ throw ServerCommonException.invalidArgument("Login ID");
+ }
+ Map request = mapOf("loginId", loginId, "ssoAppIds", ssoAppIds);
+ ApiProxy apiProxy = getApiProxy();
+ return apiProxy.post(getUri(USER_ADD_SSO_APPS_LINK), request, UserResponseDetails.class);
+ }
+
+ @Override
+ public UserResponseDetails setSsoApps(String loginId, List ssoAppIds) throws DescopeException {
+ if (StringUtils.isBlank(loginId)) {
+ throw ServerCommonException.invalidArgument("Login ID");
+ }
+ Map request = mapOf("loginId", loginId, "ssoAppIds", ssoAppIds);
+ ApiProxy apiProxy = getApiProxy();
+ return apiProxy.post(getUri(USER_SET_SSO_APPS_LINK), request, UserResponseDetails.class);
+ }
+
+ @Override
+ public UserResponseDetails removeSsoApps(String loginId, List ssoAppIds) throws DescopeException {
+ if (StringUtils.isBlank(loginId)) {
+ throw ServerCommonException.invalidArgument("Login ID");
+ }
+ Map request = mapOf("loginId", loginId, "ssoAppIds", ssoAppIds);
+ ApiProxy apiProxy = getApiProxy();
+ return apiProxy.post(getUri(USER_SET_SSO_APPS_LINK), request, UserResponseDetails.class);
+ }
+
@Override
public UserResponseDetails addTenant(String loginId, String tenantId) throws DescopeException {
if (StringUtils.isBlank(loginId)) {
diff --git a/src/test/java/com/descope/sdk/TestUtils.java b/src/test/java/com/descope/sdk/TestUtils.java
index b2f3bc4f..1f16a61d 100644
--- a/src/test/java/com/descope/sdk/TestUtils.java
+++ b/src/test/java/com/descope/sdk/TestUtils.java
@@ -52,7 +52,8 @@ public class TestUtils {
Collections.emptyMap(),
false,
false,
- Collections.emptyMap());
+ Collections.emptyMap(),
+ Collections.emptyList());
public static final JWTResponse MOCK_JWT_RESPONSE = new JWTResponse(
"someSessionJwt",
"someRefreshJwt",
diff --git a/src/test/java/com/descope/sdk/auth/impl/WebAuthnServiceImplTest.java b/src/test/java/com/descope/sdk/auth/impl/WebAuthnServiceImplTest.java
index 8886262e..e25b03a0 100644
--- a/src/test/java/com/descope/sdk/auth/impl/WebAuthnServiceImplTest.java
+++ b/src/test/java/com/descope/sdk/auth/impl/WebAuthnServiceImplTest.java
@@ -168,7 +168,7 @@ void testSignInStartEmptyOrigin() {
void testSignInStartEmptyToken() {
ServerCommonException thrown =
assertThrows(ServerCommonException.class,
- () -> webAuthnService.signInStart("x", "x", null, new LoginOptions(true, false, null)));
+ () -> webAuthnService.signInStart("x", "x", null, new LoginOptions(true, false, null, null)));
assertNotNull(thrown);
assertEquals("The Token argument is invalid", thrown.getMessage());
}
diff --git a/src/test/java/com/descope/sdk/mgmt/impl/SsoApplicationServiceImplTest.java b/src/test/java/com/descope/sdk/mgmt/impl/SsoApplicationServiceImplTest.java
new file mode 100644
index 00000000..e33b676c
--- /dev/null
+++ b/src/test/java/com/descope/sdk/mgmt/impl/SsoApplicationServiceImplTest.java
@@ -0,0 +1,94 @@
+package com.descope.sdk.mgmt.impl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.descope.exception.RateLimitExceededException;
+import com.descope.model.client.Client;
+import com.descope.model.ssoapp.OIDCApplicationRequest;
+import com.descope.model.ssoapp.SAMLApplicationRequest;
+import com.descope.model.ssoapp.SSOApplication;
+import com.descope.sdk.TestUtils;
+import com.descope.sdk.mgmt.SsoApplicationService;
+import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
+import org.junitpioneer.jupiter.RetryingTest;
+
+public class SsoApplicationServiceImplTest {
+
+ private SsoApplicationService ssoApplicationService;
+
+ @BeforeEach
+ void setUp() {
+ Client client = TestUtils.getClient();
+ this.ssoApplicationService = ManagementServiceBuilder.buildServices(client).getSsoApplicationService();
+ }
+
+ @RetryingTest(value = 3, suspendForMs = 30000, onExceptions = RateLimitExceededException.class)
+ void testFunctionalFullCycleOIDC() {
+ String name = TestUtils.getRandomName("a-");
+ String id = ssoApplicationService.createOIDCApplication(OIDCApplicationRequest.builder()
+ .name(name)
+ .description("test")
+ .enabled(true)
+ .build());
+ assertThat(id).isNotBlank();
+ SSOApplication app = ssoApplicationService.load(id);
+ assertEquals(name, app.getName());
+ assertEquals("test", app.getDescription());
+ assertEquals("oidc", app.getAppType());
+ ssoApplicationService.updateOIDCApplication(OIDCApplicationRequest.builder()
+ .name(name + "1")
+ .description("test1")
+ .enabled(false)
+ .id(id)
+ .build());
+ List apps = ssoApplicationService.loadAll();
+ boolean found = false;
+ for (SSOApplication a : apps) {
+ if (a.getId().equals(id)) {
+ found = true;
+ assertEquals("test1", a.getDescription());
+ assertEquals(name + "1", a.getName());
+ assertFalse(a.getEnabled());
+ }
+ }
+ assertTrue(found);
+ ssoApplicationService.delete(id);
+ }
+
+ @RetryingTest(value = 3, suspendForMs = 30000, onExceptions = RateLimitExceededException.class)
+ void testFunctionalFullCycleSAML() {
+ String name = TestUtils.getRandomName("a-");
+ String id = ssoApplicationService.createSAMLApplication(SAMLApplicationRequest.builder()
+ .name(name)
+ .description("test")
+ .enabled(true)
+ .build());
+ assertThat(id).isNotBlank();
+ SSOApplication app = ssoApplicationService.load(id);
+ assertEquals(name, app.getName());
+ assertEquals("test", app.getDescription());
+ assertEquals("saml", app.getAppType());
+ ssoApplicationService.updateSAMLApplication(SAMLApplicationRequest.builder()
+ .name(name + "1")
+ .description("test1")
+ .enabled(false)
+ .id(id)
+ .build());
+ List apps = ssoApplicationService.loadAll();
+ boolean found = false;
+ for (SSOApplication a : apps) {
+ if (a.getId().equals(id)) {
+ found = true;
+ assertEquals("test1", a.getDescription());
+ assertEquals(name + "1", a.getName());
+ assertFalse(a.getEnabled());
+ }
+ }
+ assertTrue(found);
+ ssoApplicationService.delete(id);
+ }
+}
diff --git a/src/test/java/com/descope/sdk/mgmt/impl/SsoServiceImplTest.java b/src/test/java/com/descope/sdk/mgmt/impl/SsoServiceImplTest.java
index 006d14ec..3fd8ec56 100644
--- a/src/test/java/com/descope/sdk/mgmt/impl/SsoServiceImplTest.java
+++ b/src/test/java/com/descope/sdk/mgmt/impl/SsoServiceImplTest.java
@@ -1,5 +1,6 @@
package com.descope.sdk.mgmt.impl;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -10,30 +11,49 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import com.descope.exception.RateLimitExceededException;
import com.descope.exception.ServerCommonException;
import com.descope.model.client.Client;
+import com.descope.model.mgmt.ManagementServices;
import com.descope.model.sso.AttributeMapping;
+import com.descope.model.sso.GroupsMapping;
+import com.descope.model.sso.OIDCAttributeMapping;
import com.descope.model.sso.RoleMapping;
+import com.descope.model.sso.SSOOIDCSettings;
+import com.descope.model.sso.SSOSAMLSettings;
+import com.descope.model.sso.SSOSAMLSettingsByMetadata;
import com.descope.model.sso.SSOSettingsResponse;
+import com.descope.model.sso.SSOTenantSettingsResponse;
import com.descope.proxy.ApiProxy;
import com.descope.proxy.impl.ApiProxyBuilder;
import com.descope.sdk.TestUtils;
+import com.descope.sdk.mgmt.RolesService;
import com.descope.sdk.mgmt.SsoService;
+import com.descope.sdk.mgmt.TenantService;
import java.util.Arrays;
+import java.util.List;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junitpioneer.jupiter.RetryingTest;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
+@SuppressWarnings("deprecation")
class SsoServiceImplTest {
private SsoService ssoService;
+ private TenantService tenantService;
+ private RolesService rolesService;
+
@BeforeEach
void setUp() {
Client client = TestUtils.getClient();
- this.ssoService = ManagementServiceBuilder.buildServices(client).getSsoService();
+ ManagementServices mgmt = ManagementServiceBuilder.buildServices(client);
+ this.ssoService = mgmt.getSsoService();
+ this.tenantService = mgmt.getTenantService();
+ this.rolesService = mgmt.getRolesService();
}
@Test
@@ -205,4 +225,100 @@ void testConfigureMappingForSuccess() {
verify(apiProxy, times(1)).post(any(), any(), any());
}
}
+
+ @RetryingTest(value = 3, suspendForMs = 30000, onExceptions = RateLimitExceededException.class)
+ void testFunctionalFullCycleOIDC() {
+ String name = TestUtils.getRandomName("t-");
+ String tenantId = tenantService.create(name, Arrays.asList(name + ".com", name + "1.com"));
+ assertThat(tenantId).isNotBlank();
+ ssoService.configureOIDCSettings(tenantId, SSOOIDCSettings.builder()
+ .jwksUrl("https://" + name + ".com/jwks")
+ .authUrl("https://" + name + ".com/auth")
+ .callbackDomain(name + ".com")
+ .clientId("xxx")
+ .clientSecret("yyy")
+ .grantType("implicit")
+ .issuer("i")
+ .name(name)
+ .redirectUrl("https://" + name + ".com/r")
+ .tokenUrl("https://" + name + ".com/t")
+ .userDataUrl("https://" + name + ".com/ud")
+ .userAttrMapping(OIDCAttributeMapping.builder()
+ .loginId("loginId")
+ .email("email")
+ .username("username")
+ .name("name")
+ .build())
+ .build(), null);
+ SSOTenantSettingsResponse resp = ssoService.loadSettings(tenantId);
+ assertEquals(tenantId, resp.getTenant().getId());
+ assertThat(Arrays.asList(name + ".com", name + "1.com")).containsExactly(name + ".com", name + "1.com");
+ assertEquals(name, resp.getTenant().getName());
+ assertEquals("https://" + name + ".com/jwks", resp.getOidc().getJwksUrl());
+ assertEquals("https://" + name + ".com/auth", resp.getOidc().getAuthUrl());
+ assertEquals(name + ".com", resp.getOidc().getCallbackDomain());
+ assertEquals("xxx", resp.getOidc().getClientId());
+ assertEquals("implicit", resp.getOidc().getGrantType());
+ assertEquals("i", resp.getOidc().getIssuer());
+ assertEquals(name, resp.getOidc().getName());
+ assertEquals("https://" + name + ".com/r", resp.getOidc().getRedirectUrl());
+ assertEquals("https://" + name + ".com/t", resp.getOidc().getTokenUrl());
+ assertEquals("https://" + name + ".com/ud", resp.getOidc().getUserDataUrl());
+ ssoService.deleteSettings(tenantId);
+ tenantService.delete(tenantId);
+ }
+
+ @RetryingTest(value = 3, suspendForMs = 30000, onExceptions = RateLimitExceededException.class)
+ void testFunctionalFullCycleSAML() {
+ String name = TestUtils.getRandomName("t-");
+ String tenantId = tenantService.create(name, Arrays.asList(name + ".com", name + "1.com"));
+ assertThat(tenantId).isNotBlank();
+ ssoService.configureSAMLSettings(tenantId, SSOSAMLSettings.builder()
+ .attributeMapping(AttributeMapping.builder()
+ .email("email")
+ .name("name")
+ .build())
+ .entityId("entityId")
+ .idpCert("idpCert")
+ .idpUrl("https://" + name + ".com")
+ .build(), "https://" + name + ".com", null);
+ SSOTenantSettingsResponse resp = ssoService.loadSettings(tenantId);
+ assertEquals(tenantId, resp.getTenant().getId());
+ assertThat(Arrays.asList(name + ".com", name + "1.com")).containsExactly(name + ".com", name + "1.com");
+ assertEquals(name, resp.getTenant().getName());
+ assertEquals("entityId", resp.getSaml().getIdpEntityId());
+ assertEquals("idpCert", resp.getSaml().getIdpCertificate());
+ assertEquals("https://" + name + ".com", resp.getSaml().getIdpSSOUrl());
+ assertEquals("https://" + name + ".com", resp.getSaml().getRedirectUrl());
+ ssoService.deleteSettings(tenantId);
+ tenantService.delete(tenantId);
+ }
+
+ @RetryingTest(value = 3, suspendForMs = 30000, onExceptions = RateLimitExceededException.class)
+ void testFunctionalFullCycleSAMLMetadata() {
+ String name = TestUtils.getRandomName("t-");
+ String tenantId = tenantService.create(name, Arrays.asList(name + ".com", name + "1.com"));
+ assertThat(tenantId).isNotBlank();
+ String roleName = TestUtils.getRandomName("rt-").substring(0, 20);
+ rolesService.create(roleName, tenantId, "ttt", null);
+ ssoService.configureSAMLSettingsByMetadata(tenantId, SSOSAMLSettingsByMetadata.builder()
+ .attributeMapping(AttributeMapping.builder()
+ .email("email")
+ .name("name")
+ .build())
+ .idpMetadataUrl("https://" + name + ".com/md")
+ .roleMappings(Arrays.asList(RoleMapping.builder().groups(Arrays.asList("a", "b")).roleName(roleName).build()))
+ .build(), "https://" + name + ".com", null);
+ SSOTenantSettingsResponse resp = ssoService.loadSettings(tenantId);
+ assertEquals(tenantId, resp.getTenant().getId());
+ assertThat(Arrays.asList(name + ".com", name + "1.com")).containsExactly(name + ".com", name + "1.com");
+ assertEquals(name, resp.getTenant().getName());
+ assertEquals("https://" + name + ".com/md", resp.getSaml().getIdpMetadataUrl());
+ List groupsMapping = resp.getSaml().getGroupsMapping();
+ assertNotNull(groupsMapping);
+ assertThat(groupsMapping).hasSize(1);
+ assertThat(groupsMapping.get(0).getRole().getId()).isNotBlank();
+ ssoService.deleteSettings(tenantId);
+ tenantService.delete(tenantId);
+ }
}