From 288dd00ca1b76f0c317efa743b857f71bb9a0967 Mon Sep 17 00:00:00 2001 From: jensp Date: Sat, 24 Aug 2019 12:48:14 +0000 Subject: [PATCH] First parts of SAML login module git-svn-id: https://svn.libreccm.org/ccm/trunk@6167 8810af33-2d31-482b-a856-94f89814c4df --- .../kernel/security/SamlLoginModule.java | 89 +++++++ .../kernel/security/SecurityConfig.java | 235 ++++++++++++++---- .../SecurityConfig_parameter.properties | 75 ++++++ 3 files changed, 356 insertions(+), 43 deletions(-) create mode 100644 ccm-core/src/com/arsdigita/kernel/security/SamlLoginModule.java diff --git a/ccm-core/src/com/arsdigita/kernel/security/SamlLoginModule.java b/ccm-core/src/com/arsdigita/kernel/security/SamlLoginModule.java new file mode 100644 index 000000000..91d700964 --- /dev/null +++ b/ccm-core/src/com/arsdigita/kernel/security/SamlLoginModule.java @@ -0,0 +1,89 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.arsdigita.kernel.security; + +import java.io.IOException; +import java.util.Map; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * + * @author Jens Pelzetter + */ +public class SamlLoginModule implements LoginModule { + + private CallbackHandler callbackHandler; + private HttpServletRequest request; + private HttpServletResponse response; + private Subject subject; + + @Override + public void initialize(final Subject subject, + final CallbackHandler callbackHandler, + final Map sharedState, + final Map options) { + + this.callbackHandler = callbackHandler; + this.subject = subject; + } + + @Override + public boolean login() throws LoginException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public boolean commit() throws LoginException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public boolean abort() throws LoginException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public boolean logout() throws LoginException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + protected HttpServletRequest getRequest() throws LoginException { + + try { + if (request == null) { + final HTTPRequestCallback callback = new HTTPRequestCallback(); + callbackHandler.handle(new Callback[]{callback}); + request = callback.getRequest(); + } + return request; + } catch (IOException | UnsupportedCallbackException ex) { + throw new LoginException("Could not get HTTP request" + ex); + } + } + + protected HttpServletResponse getResponse() throws LoginException { + + try { + if (response == null) { + final HTTPResponseCallback callback = new HTTPResponseCallback(); + callbackHandler.handle(new Callback[]{callback}); + response = callback.getResponse(); + } + return response; + } catch (IOException | UnsupportedCallbackException ex) { + throw new LoginException("Could not get HTTP response" + ex); + } + } + +} diff --git a/ccm-core/src/com/arsdigita/kernel/security/SecurityConfig.java b/ccm-core/src/com/arsdigita/kernel/security/SecurityConfig.java index 3a0c15e3c..35c25e8c1 100755 --- a/ccm-core/src/com/arsdigita/kernel/security/SecurityConfig.java +++ b/ccm-core/src/com/arsdigita/kernel/security/SecurityConfig.java @@ -36,8 +36,8 @@ import org.apache.log4j.Logger; /** * A record containing server-session scoped security configuration properties. * - * Accessors of this class may return null. Developers should take care - * to trap null return values in their code. + * Accessors of this class may return null. Developers should take care to trap + * null return values in their code. * * * @author Rafael H. Schloming <rhs@mit.edu> @@ -49,24 +49,30 @@ public class SecurityConfig extends AbstractConfig { private static final Logger s_log = Logger.getLogger(SecurityConfig.class); private static SecurityConfig s_config = null; private static String s_systemAdministratorEmailAddress = null; - /** Size of secret key in bytes. **/ + /** + * Size of secret key in bytes. * + */ public static int SECRET_KEY_BYTES = 16; - /** The class name of the SecurityHelper implementation. Must implement - SecurityHelper interface */ + /** + * The class name of the SecurityHelper implementation. Must implement + * SecurityHelper interface + */ private final Parameter m_securityHelperClass = new SpecificClassParameter( - "waf.security_helper_class", Parameter.REQUIRED, - com.arsdigita.kernel.security.DefaultSecurityHelper.class, - com.arsdigita.kernel.security.SecurityHelper.class); + "waf.security_helper_class", Parameter.REQUIRED, + com.arsdigita.kernel.security.DefaultSecurityHelper.class, + com.arsdigita.kernel.security.SecurityHelper.class); // /** This parameter is obsolete. */ // private final Parameter m_sessionTrackingMethod = new StringParameter // ("waf.session_tracking_method", Parameter.REQUIRED, "cookie"); - /** List of extensions excluded from authentication cookies. - * Authentication is checked for all requests, but requests with one of - * these extensions will never cause a new cookie to be set. - * Include a leading dot for each extension. */ + /** + * List of extensions excluded from authentication cookies. Authentication + * is checked for all requests, but requests with one of these extensions + * will never cause a new cookie to be set. Include a leading dot for each + * extension. + */ private final Parameter m_excludedExtensions = new StringArrayParameter( - "waf.excluded_extensions", Parameter.REQUIRED, - new String[]{".jpg", ".gif", ".png", ".pdf"}); + "waf.excluded_extensions", Parameter.REQUIRED, + new String[]{".jpg", ".gif", ".png", ".pdf"}); // ///////////////////////////////////////////////////////////////////////////// // This section completely moved to com.arsdigita.ui.UIConfig. // Configuration is not an Initializer task. @@ -107,32 +113,97 @@ public class SecurityConfig extends AbstractConfig { // ("waf.pagemap.perm_single", Parameter.REQUIRED, "permissions/one"); // //////////////////////////////////////////////////////////////////////////// private final Parameter m_cookieDurationMinutes = new IntegerParameter( - "waf.pagemap.cookies_duration_minutes", Parameter.OPTIONAL, null); + "waf.pagemap.cookies_duration_minutes", Parameter.OPTIONAL, null); private final Parameter m_cookieDomain = new StringParameter( - "waf.cookie_domain", Parameter.OPTIONAL, null); + "waf.cookie_domain", Parameter.OPTIONAL, null); private final Parameter m_loginConfig = new StringArrayParameter( - "waf.login_config", Parameter.REQUIRED, - new String[]{ - "Request:com.arsdigita.kernel.security.AdminLoginModule:sufficient", - "Request:com.arsdigita.kernel.security.RecoveryLoginModule:sufficient", - "Request:com.arsdigita.kernel.security.CookieLoginModule:requisite", - "Register:com.arsdigita.kernel.security.LocalLoginModule:requisite", - "Register:com.arsdigita.kernel.security.UserIDLoginModule:requisite", - "Register:com.arsdigita.kernel.security.CookieLoginModule:optional", - "RegisterSSO:com.arsdigita.kernel.security.SimpleSSOLoginModule:requisite", - "RegisterSSO:com.arsdigita.kernel.security.CookieLoginModule:optional" - }); + "waf.login_config", Parameter.REQUIRED, + new String[]{ + "Request:com.arsdigita.kernel.security.AdminLoginModule:sufficient", + "Request:com.arsdigita.kernel.security.RecoveryLoginModule:sufficient", + "Request:com.arsdigita.kernel.security.CookieLoginModule:requisite", + "Register:com.arsdigita.kernel.security.LocalLoginModule:requisite", + "Register:com.arsdigita.kernel.security.UserIDLoginModule:requisite", + "Register:com.arsdigita.kernel.security.CookieLoginModule:optional", + "RegisterSSO:com.arsdigita.kernel.security.SimpleSSOLoginModule:requisite", + "RegisterSSO:com.arsdigita.kernel.security.CookieLoginModule:optional" + }); private final Parameter m_adminEmail = new StringParameter( - "waf.admin.contact_email", Parameter.OPTIONAL, null); + "waf.admin.contact_email", Parameter.OPTIONAL, null); private final Parameter m_autoRegistrationOn = new BooleanParameter( - "waf.auto_registration_on", Parameter.REQUIRED, Boolean.TRUE); - private final Parameter m_userBanOn = new BooleanParameter("waf.user_ban_on", - Parameter.REQUIRED, - Boolean.FALSE); + "waf.auto_registration_on", Parameter.REQUIRED, Boolean.TRUE); + private final Parameter m_userBanOn + = new BooleanParameter("waf.user_ban_on", + Parameter.REQUIRED, + Boolean.FALSE); private final Parameter m_enableQuestion = new BooleanParameter( - "waf.user_question.enable", Parameter.REQUIRED, Boolean.FALSE); + "waf.user_question.enable", Parameter.REQUIRED, Boolean.FALSE); - /** + private final Parameter m_enableSaml = new BooleanParameter( + "waf.enable_saml", Parameter.REQUIRED, Boolean.FALSE); + private final Parameter m_oneLoginSaml2Strict = new BooleanParameter( + "waf.onelogin.saml2.strict", Parameter.REQUIRED, Boolean.TRUE); + private final Parameter m_oneLoginSaml2Debug = new BooleanParameter( + "waf.onelogin.saml2.debug", Parameter.REQUIRED, Boolean.FALSE); + private final Parameter m_oneLoginSaml2SpEntityId = new StringParameter( + "waf.onelogin.saml2.sp.entityid", + Parameter.REQUIRED, + "http://localhost:8080/ccm-saml/metadata"); + private final Parameter m_oneLoginSaml2SpAssertationConsumerServiceUrl + = new StringParameter( + "waf.onelogin.saml2.sp.assertion_consumer_service.url", + Parameter.REQUIRED, + "http://localhost:8080/ccm-saml/acs"); + private final Parameter m_oneLoginSaml2SpAssertationConsumerServiceBinding + = new StringParameter( + "waf.onelogin.saml2.sp.assertion_consumer_service.binding", + Parameter.REQUIRED, + "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"); + private final Parameter m_oneLoginSaml2SpSingleLogoutServiceUrl + = new StringParameter( + "waf.onelogin.saml2.sp.single_logout_service.url", + Parameter.REQUIRED, + "http://localhost:8080/ccm-saml/sls"); + private final Parameter m_oneLoginSaml2SpSingleLogoutServiceBinding + = new StringParameter( + "waf.onelogin.saml2.sp.single_logout_service.binding", + Parameter.REQUIRED, + "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"); + private final Parameter m_oneLoginSaml2SpNameIdFormat = new StringParameter( + "waf.onelogin.saml2.sp.nameidformat", + Parameter.REQUIRED, + "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"); + private final Parameter m_oneLoginSaml2IdpEntityId = new StringParameter( + "waf.onelogin.saml2.idp.entityid", + Parameter.REQUIRED, + ""); + private final Parameter m_oneLoginSaml2IdpSingleSignOnServiceUrl + = new StringParameter( + "waf.onelogin.saml2.idp.single_sign_on_service.url", + Parameter.REQUIRED, + ""); + private final Parameter m_oneLoginSaml2IdpSingleSignOnServiceBinding + = new StringParameter( + "waf.onelogin.saml2.idp.single_sign_on_service.binding", + Parameter.REQUIRED, + ""); + private final Parameter m_oneLoginSaml2IdpSingleLogoutServiceUrl + = new StringParameter( + "waf.onelogin.saml2.idp.single_logout_service.url", + Parameter.REQUIRED, + ""); + private final Parameter m_oneLoginSaml2IdpSingleLogoutServiceResponseUrl + = new StringParameter( + "waf.onelogin.saml2.idp.single_logout_service.response.url", + Parameter.REQUIRED, + ""); + private final Parameter m_oneLoginSaml2IdpSingleLogoutServiceBinding + = new StringParameter( + "waf.onelogin.saml2.idp.single_logout_service.binding", + Parameter.REQUIRED, + "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"); + + /** * Constructs an empty SecurityConfig object */ public SecurityConfig() { @@ -149,12 +220,28 @@ public class SecurityConfig extends AbstractConfig { register(m_userBanOn); register(m_enableQuestion); + register(m_enableSaml); + + register(m_oneLoginSaml2Debug); + register(m_oneLoginSaml2IdpEntityId); + register(m_oneLoginSaml2IdpSingleLogoutServiceBinding); + register(m_oneLoginSaml2IdpSingleLogoutServiceResponseUrl); + register(m_oneLoginSaml2IdpSingleLogoutServiceUrl); + register(m_oneLoginSaml2IdpSingleSignOnServiceBinding); + register(m_oneLoginSaml2IdpSingleSignOnServiceUrl); + register(m_oneLoginSaml2SpAssertationConsumerServiceBinding); + register(m_oneLoginSaml2SpAssertationConsumerServiceUrl); + register(m_oneLoginSaml2SpEntityId); + register(m_oneLoginSaml2SpNameIdFormat); + register(m_oneLoginSaml2SpSingleLogoutServiceBinding); + register(m_oneLoginSaml2SpSingleLogoutServiceUrl); + register(m_oneLoginSaml2Strict); + loadInfo(); } /** - * Returns the singleton configuration record for the runtime - * environment. + * Returns the singleton configuration record for the runtime environment. * * @return The RuntimeConfig record; it cannot be null */ @@ -168,7 +255,7 @@ public class SecurityConfig extends AbstractConfig { } /** - * + * * @return */ public final Class getSecurityHelperClass() { @@ -183,7 +270,7 @@ public class SecurityConfig extends AbstractConfig { // return (String) get(m_sessionTrackingMethod); // } /** - * + * * @return */ public final List getExcludedExtensions() { @@ -213,22 +300,21 @@ public class SecurityConfig extends AbstractConfig { } return email; } - + public Boolean getEnableQuestion() { return (Boolean) get(m_enableQuestion); } private static synchronized String getSystemAdministratorEmailAddress() { if (s_systemAdministratorEmailAddress == null) { - ObjectPermissionCollection perms = - PermissionService. - getGrantedUniversalPermissions(); + ObjectPermissionCollection perms = PermissionService. + getGrantedUniversalPermissions(); perms.addEqualsFilter("granteeIsUser", Boolean.TRUE); perms.clearOrder(); perms.addOrder("granteeID"); if (perms.next()) { s_systemAdministratorEmailAddress = perms.getGranteeEmail(). - toString(); + toString(); perms.close(); } else { // Haven't found anything. We don't want to repeat this query @@ -242,4 +328,67 @@ public class SecurityConfig extends AbstractConfig { public final boolean isAutoRegistrationOn() { return ((Boolean) get(m_autoRegistrationOn)).booleanValue(); } + + public final boolean getEnableSaml() { + return (Boolean) get(m_enableSaml); + } + + public final Boolean getOneLoginSaml2Strict() { + return (Boolean) get(m_oneLoginSaml2Strict); + } + + public final Boolean getM_oneLoginSaml2Debug() { + return (boolean) get(m_oneLoginSaml2Debug); + } + + public final String getM_oneLoginSaml2SpEntityId() { + return (String) get(m_oneLoginSaml2SpEntityId); + } + + public final String getOneLoginSaml2SpAssertationConsumerServiceUrl() { + return (String) get(m_oneLoginSaml2SpAssertationConsumerServiceUrl); + } + + public final String getOneLoginSaml2SpAssertationConsumerServiceBinding() { + return (String) get(m_oneLoginSaml2SpAssertationConsumerServiceBinding); + } + + public final String getOneLoginSaml2SpSingleLogoutServiceUrl() { + return (String) get(m_oneLoginSaml2SpSingleLogoutServiceUrl); + } + + public final String getOneLoginSaml2SpSingleLogoutServiceBinding() { + return (String) get(m_oneLoginSaml2SpSingleLogoutServiceBinding); + } + + public final String getOneLoginSaml2SpNameIdFormat() { + return (String) get(m_oneLoginSaml2SpNameIdFormat); + } + + public final String getOneLoginSaml2IdpEntityId() { + return (String) get(m_oneLoginSaml2IdpEntityId); + } + + public final String getOneLoginSaml2IdpSingleSignOnServiceUrl() { + return (String) get(m_oneLoginSaml2IdpSingleSignOnServiceUrl); + } + + public final String getOneLoginSaml2IdpSingleSignOnServiceBinding() { + return (String) get(m_oneLoginSaml2IdpSingleSignOnServiceBinding); + } + + public final String getOneLoginSaml2IdpSingleLogoutServiceUrl() { + return (String) get(m_oneLoginSaml2IdpSingleLogoutServiceUrl); + } + + public final String getOneLoginSaml2IdpSingleLogoutServiceResponseUrl() { + return (String) get(m_oneLoginSaml2IdpSingleLogoutServiceResponseUrl); + } + + public final String getOneLoginSaml2IdpSingleLogoutServiceBinding() { + return (String) get(m_oneLoginSaml2IdpSingleLogoutServiceBinding); + } + + + } diff --git a/ccm-core/src/com/arsdigita/kernel/security/SecurityConfig_parameter.properties b/ccm-core/src/com/arsdigita/kernel/security/SecurityConfig_parameter.properties index 2976be7d4..91434ea77 100755 --- a/ccm-core/src/com/arsdigita/kernel/security/SecurityConfig_parameter.properties +++ b/ccm-core/src/com/arsdigita/kernel/security/SecurityConfig_parameter.properties @@ -28,6 +28,81 @@ waf.user_question_enable.purpose=Enable question if a user has forgotten its pas waf.user_question_enable.example=false waf.user_question_enable.format=true|false +waf.enable_saml.title=Enable SAML +waf.enable_saml.purpose=Enable authentication via SAML +waf.enable_saml.example=false +waf.enable_saml.format=true|false + +waf.onelogin.saml2.strict.title=Strict mode for OneLogin +waf.onelogin.saml2.strict.purpose= +waf.onelogin.saml2.strict.example=true +waf.onelogin.saml2.strict.format=true|false + +waf.onelogin.saml2.debug.title=Enable OneLogin debug messages +waf.onelogin.saml2.debug.purpose=Show debug messages for SAML +waf.onelogin.saml2.debug.example=false +waf.onelogin.saml2.debug.format=true|false + +waf.onelogin.saml2.sp.entityid.title=URL to informations about this application. +waf.onelogin.saml2.sp.entityid.purpose +waf.onelogin.saml2.sp.entityid.example=http://localhost:8080/ccm-saml/ +waf.onelogin.saml2.sp.entityid.format=[string] + +waf.onelogin.saml2.sp.assertion_consumer_service.url.title=Consumer service URL +waf.onelogin.saml2.sp.assertion_consumer_service.url.purpose +waf.onelogin.saml2.sp.assertion_consumer_service.url.example= +waf.onelogin.saml2.sp.assertion_consumer_service.url.format=[string] + +waf.onelogin.saml2.sp.assertion_consumer_service.binding.title=Service binding +waf.onelogin.saml2.sp.assertion_consumer_service.binding.purpose= +waf.onelogin.saml2.sp.assertion_consumer_service.binding.example= +waf.onelogin.saml2.sp.assertion_consumer_service.binding.format=[string] + +waf.onelogin.saml2.sp.single_logout_service.url.title=Logout URL +waf.onelogin.saml2.sp.single_logout_service.url.purpose= +waf.onelogin.saml2.sp.single_logout_service.url.example= +waf.onelogin.saml2.sp.single_logout_service.url.format=[string] + +waf.onelogin.saml2.sp.single_logout_service.binding.title=Logout binding +waf.onelogin.saml2.sp.single_logout_service.binding.purpose= +waf.onelogin.saml2.sp.single_logout_service.binding.example= +waf.onelogin.saml2.sp.single_logout_service.binding.format=[string] + +waf.onelogin.saml2.sp.nameidformat.title=Name ID format +waf.onelogin.saml2.sp.nameidformat.purpose= +waf.onelogin.saml2.sp.nameidformat.example= +waf.onelogin.saml2.sp.nameidformat.format=[string] + +waf.onelogin.saml2.idp.entityid.title=IDP Entity ID +waf.onelogin.saml2.idp.entityid.purpose= +waf.onelogin.saml2.idp.entityid.example= +waf.onelogin.saml2.idp.entityid.format=[string] + +waf.onelogin.saml2.idp.single_sign_on_service.url.title=Single Sign On Service URL +waf.onelogin.saml2.idp.single_sign_on_service.url.purpose= +waf.onelogin.saml2.idp.single_sign_on_service.url.example= +waf.onelogin.saml2.idp.single_sign_on_service.url.format=[string] + +waf.onelogin.saml2.idp.single_sign_on_service.binding.title=Single Sign On Service URL +waf.onelogin.saml2.idp.single_sign_on_service.binding.purpose= +waf.onelogin.saml2.idp.single_sign_on_service.binding.example= +waf.onelogin.saml2.idp.single_sign_on_service.binding.format=[string] + +waf.onelogin.saml2.idp.single_logout_service.url.title=Single Logout Service URL +waf.onelogin.saml2.idp.single_logout_service.url.purpose= +waf.onelogin.saml2.idp.single_logout_service.url.example= +waf.onelogin.saml2.idp.single_logout_service.url.format=[string] + +waf.onelogin.saml2.idp.single_logout_service.response.url.title=Single Logout Service Response URL +waf.onelogin.saml2.idp.single_logout_service.response.url.purpose= +waf.onelogin.saml2.idp.single_logout_service.response.url.example= +waf.onelogin.saml2.idp.single_logout_service.response.url.format=[string] + +waf.onelogin.saml2.idp.single_logout_service.binding.title=Logout Service Binding +waf.onelogin.saml2.idp.single_logout_service.binding.purpose= +waf.onelogin.saml2.idp.single_logout_service.binding.example= +waf.onelogin.saml2.idp.single_logout_service.binding.format=[string] + # Moved to com.arsdigita.ui.UIConfig (2011-02). # Retained here for easy reference during transition phase # waf.pagemap.root.title=Root Page