浏览代码

oauth enhancements

jrivard@gmail.com 6 年之前
父节点
当前提交
a367b7c091
共有 26 个文件被更改,包括 753 次插入459 次删除
  1. 2 0
      server/src/main/java/password/pwm/AppProperty.java
  2. 2 0
      server/src/main/java/password/pwm/config/PwmSetting.java
  3. 12 0
      server/src/main/java/password/pwm/http/PwmURL.java
  4. 13 0
      server/src/main/java/password/pwm/http/SessionManager.java
  5. 43 0
      server/src/main/java/password/pwm/http/auth/AuthenticationMethod.java
  6. 100 0
      server/src/main/java/password/pwm/http/auth/BasicFilterAuthenticationProvider.java
  7. 2 2
      server/src/main/java/password/pwm/http/auth/CASFilterAuthenticationProvider.java
  8. 35 0
      server/src/main/java/password/pwm/http/auth/HttpAuthRecord.java
  9. 160 0
      server/src/main/java/password/pwm/http/auth/HttpAuthenticationUtilities.java
  10. 59 0
      server/src/main/java/password/pwm/http/auth/OAuthFilterAuthenticationProvider.java
  11. 1 1
      server/src/main/java/password/pwm/http/auth/PwmHttpFilterAuthenticationProvider.java
  12. 86 0
      server/src/main/java/password/pwm/http/auth/SSOHeaderFilterAuthenticationProvider.java
  13. 23 313
      server/src/main/java/password/pwm/http/filter/AuthenticationFilter.java
  14. 13 4
      server/src/main/java/password/pwm/http/servlet/LoginServlet.java
  15. 28 9
      server/src/main/java/password/pwm/http/servlet/PwmServletDefinition.java
  16. 4 4
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java
  17. 13 34
      server/src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java
  18. 31 9
      server/src/main/java/password/pwm/http/servlet/oauth/OAuthMachine.java
  19. 2 0
      server/src/main/java/password/pwm/http/servlet/oauth/OAuthSettings.java
  20. 9 1
      server/src/main/java/password/pwm/http/state/CryptoCookieLoginImpl.java
  21. 2 26
      server/src/main/java/password/pwm/ldap/auth/AuthenticationResult.java
  22. 98 54
      server/src/main/java/password/pwm/ldap/auth/LDAPAuthenticationRequest.java
  23. 5 0
      server/src/main/java/password/pwm/util/logging/PwmLogger.java
  24. 2 0
      server/src/main/resources/password/pwm/AppProperty.properties
  25. 4 0
      server/src/main/resources/password/pwm/config/PwmSetting.xml
  26. 4 2
      server/src/main/resources/password/pwm/i18n/PwmSetting.properties

+ 2 - 0
server/src/main/java/password/pwm/AppProperty.java

@@ -47,6 +47,7 @@ public enum AppProperty
     AUDIT_SYSLOG_CEF_HEADER_VENDOR                  ( "audit.syslog.cef.header.vendor" ),
     AUDIT_SYSLOG_MAX_MESSAGE_LENGTH                 ( "audit.syslog.message.length" ),
     AUDIT_SYSLOG_TRUNCATE_MESSAGE                   ( "audit.syslog.message.truncateMsg" ),
+    AUTH_ALLOW_SSO_WITH_UNKNOWN_PW                  ( "auth.allowSSOwithUnknownPassword" ),
     BACKUP_LOCATION                                 ( "backup.path" ),
     BACKUP_CONFIG_COUNT                             ( "backup.config.count" ),
     BACKUP_LOCALDB_COUNT                            ( "backup.localdb.count" ),
@@ -157,6 +158,7 @@ public enum AppProperty
     HTTP_PARAM_OAUTH_RESPONSE_TYPE                  ( "http.parameter.oauth.responseType" ),
     HTTP_PARAM_OAUTH_REDIRECT_URI                   ( "http.parameter.oauth.redirectUri" ),
     HTTP_PARAM_OAUTH_REFRESH_TOKEN                  ( "http.parameter.oauth.refreshToken" ),
+    HTTP_PARAM_OAUTH_SCOPE                          ( "http.parameter.oauth.scope" ),
     HTTP_PARAM_OAUTH_STATE                          ( "http.parameter.oauth.state" ),
     HTTP_PARAM_OAUTH_GRANT_TYPE                     ( "http.parameter.oauth.grantType" ),
     HTTP_DOWNLOAD_BUFFER_SIZE                       ( "http.download.buffer.size" ),

+ 2 - 0
server/src/main/java/password/pwm/config/PwmSetting.java

@@ -1148,6 +1148,8 @@ public enum PwmSetting
     // OAuth
     OAUTH_ID_LOGIN_URL(
             "oauth.idserver.loginUrl", PwmSettingSyntax.STRING, PwmSettingCategory.OAUTH ),
+    OAUTH_ID_SCOPE(
+            "oauth.idserver.scope", PwmSettingSyntax.STRING, PwmSettingCategory.OAUTH ),
     OAUTH_ID_CODERESOLVE_URL(
             "oauth.idserver.codeResolveUrl", PwmSettingSyntax.STRING, PwmSettingCategory.OAUTH ),
     OAUTH_ID_ATTRIBUTES_URL(

+ 12 - 0
server/src/main/java/password/pwm/http/PwmURL.java

@@ -228,6 +228,18 @@ public class PwmURL
         return isPwmServletURL( PwmServletDefinition.UpdateProfile );
     }
 
+    public PwmServletDefinition forServletDefinition()
+    {
+        for ( final PwmServletDefinition pwmServletDefinition : PwmServletDefinition.values() )
+        {
+            if ( isPwmServletURL( pwmServletDefinition ) )
+            {
+                return pwmServletDefinition;
+            }
+        }
+        return null;
+    }
+
     public boolean isLocalizable( )
     {
         return !isConfigGuideURL()

+ 13 - 0
server/src/main/java/password/pwm/http/SessionManager.java

@@ -42,6 +42,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.LdapPermissionTester;
 import password.pwm.ldap.UserInfo;
+import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.util.PasswordData;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
@@ -74,11 +75,23 @@ public class SessionManager
     {
         if ( chaiProvider == null )
         {
+            if ( isAuthenticatedWithoutPasswordAndBind() )
+            {
+                throw PwmUnrecoverableException.newException( PwmError.ERROR_PASSWORD_REQUIRED, "password required for this operation" );
+            }
             throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_AUTHENTICATION_REQUIRED, "ldap connection is not available for session" ) );
         }
         return chaiProvider;
     }
 
+    public boolean isAuthenticatedWithoutPasswordAndBind()
+    {
+        return pwmSession.getLoginInfoBean().getUserCurrentPassword() == null
+                && pwmSession.getLoginInfoBean().getType() == AuthenticationType.AUTH_WITHOUT_PASSWORD
+                && chaiProvider == null;
+
+    }
+
     public void setChaiProvider( final ChaiProvider chaiProvider )
     {
         this.chaiProvider = chaiProvider;

+ 43 - 0
server/src/main/java/password/pwm/http/auth/AuthenticationMethod.java

@@ -0,0 +1,43 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2018 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.http.auth;
+
+public enum AuthenticationMethod
+{
+    BASIC_AUTH( BasicFilterAuthenticationProvider.class.getName() ),
+    SSO_AUTH_HEADER( SSOHeaderFilterAuthenticationProvider.class.getName() ),
+    CAS( "password.pwm.http.auth.CASFilterAuthenticationProvider" ),
+    OAUTH( OAuthFilterAuthenticationProvider.class.getName() );
+
+    private final String className;
+
+    AuthenticationMethod( final String className )
+    {
+        this.className = className;
+    }
+
+    public String getClassName( )
+    {
+        return className;
+    }
+}

+ 100 - 0
server/src/main/java/password/pwm/http/auth/BasicFilterAuthenticationProvider.java

@@ -0,0 +1,100 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2018 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.http.auth;
+
+import password.pwm.PwmApplication;
+import password.pwm.bean.UserIdentity;
+import password.pwm.config.PwmSetting;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmException;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.PwmRequest;
+import password.pwm.http.PwmSession;
+import password.pwm.ldap.auth.PwmAuthenticationSource;
+import password.pwm.ldap.auth.SessionAuthenticator;
+import password.pwm.ldap.search.UserSearchEngine;
+import password.pwm.svc.stats.Statistic;
+import password.pwm.svc.stats.StatisticsManager;
+import password.pwm.util.BasicAuthInfo;
+import password.pwm.util.logging.PwmLogger;
+
+public class BasicFilterAuthenticationProvider implements PwmHttpFilterAuthenticationProvider
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( BasicFilterAuthenticationProvider.class );
+
+    @Override
+    public void attemptAuthentication(
+            final PwmRequest pwmRequest
+    )
+            throws PwmUnrecoverableException
+    {
+        if ( !pwmRequest.getConfig().readSettingAsBoolean( PwmSetting.BASIC_AUTH_ENABLED ) )
+        {
+            return;
+        }
+
+        if ( pwmRequest.isAuthenticated() )
+        {
+            return;
+        }
+
+        final BasicAuthInfo basicAuthInfo = BasicAuthInfo.parseAuthHeader( pwmRequest.getPwmApplication(), pwmRequest );
+        if ( basicAuthInfo == null )
+        {
+            return;
+        }
+
+        try
+        {
+            final PwmSession pwmSession = pwmRequest.getPwmSession();
+            final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
+
+            //user isn't already authenticated and has an auth header, so try to auth them.
+            LOGGER.debug( pwmSession, () -> "attempting to authenticate user using basic auth header (username=" + basicAuthInfo.getUsername() + ")" );
+            final SessionAuthenticator sessionAuthenticator = new SessionAuthenticator(
+                    pwmApplication,
+                    pwmSession,
+                    PwmAuthenticationSource.BASIC_AUTH
+            );
+            final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
+            final UserIdentity userIdentity = userSearchEngine.resolveUsername( basicAuthInfo.getUsername(), null, null, pwmSession.getLabel() );
+            sessionAuthenticator.authenticateUser( userIdentity, basicAuthInfo.getPassword() );
+            pwmSession.getLoginInfoBean().setBasicAuth( basicAuthInfo );
+
+        }
+        catch ( PwmException e )
+        {
+            if ( e.getError() == PwmError.ERROR_DIRECTORY_UNAVAILABLE )
+            {
+                StatisticsManager.incrementStat( pwmRequest, Statistic.LDAP_UNAVAILABLE_COUNT );
+            }
+            throw new PwmUnrecoverableException( e.getError() );
+        }
+    }
+
+    @Override
+    public boolean hasRedirectedResponse( )
+    {
+        return false;
+    }
+}

+ 2 - 2
server/src/main/java/password/pwm/util/CASFilterAuthenticationProvider.java → server/src/main/java/password/pwm/http/auth/CASFilterAuthenticationProvider.java

@@ -20,7 +20,7 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-package password.pwm.util;
+package password.pwm.http.auth;
 
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import org.jasig.cas.client.authentication.AttributePrincipal;
@@ -31,7 +31,6 @@ import org.jasig.cas.client.util.XmlUtils;
 import org.jasig.cas.client.validation.Assertion;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
-import password.pwm.PwmHttpFilterAuthenticationProvider;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.value.FileValue;
 import password.pwm.config.value.FileValue.FileContent;
@@ -44,6 +43,7 @@ import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.ldap.auth.PwmAuthenticationSource;
 import password.pwm.ldap.auth.SessionAuthenticator;
+import password.pwm.util.PasswordData;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 

+ 35 - 0
server/src/main/java/password/pwm/http/auth/HttpAuthRecord.java

@@ -0,0 +1,35 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2018 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.http.auth;
+
+import lombok.Value;
+
+import java.io.Serializable;
+import java.time.Instant;
+
+@Value
+public class HttpAuthRecord implements Serializable
+{
+    private Instant date;
+    private String guid;
+}

+ 160 - 0
server/src/main/java/password/pwm/http/auth/HttpAuthenticationUtilities.java

@@ -0,0 +1,160 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2018 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.http.auth;
+
+import password.pwm.AppProperty;
+import password.pwm.bean.LoginInfoBean;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmException;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.ProcessStatus;
+import password.pwm.http.PwmHttpResponseWrapper;
+import password.pwm.http.PwmRequest;
+import password.pwm.ldap.auth.AuthenticationType;
+import password.pwm.util.logging.PwmLogger;
+
+import javax.servlet.ServletException;
+import java.io.IOException;
+import java.time.Instant;
+import java.util.HashSet;
+import java.util.Set;
+
+public abstract class HttpAuthenticationUtilities
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( HttpAuthenticationUtilities.class );
+
+    private static final Set<AuthenticationMethod> IGNORED_AUTH_METHODS = new HashSet<>();
+
+
+    public static ProcessStatus attemptAuthenticationMethods( final PwmRequest pwmRequest ) throws IOException, ServletException
+    {
+        if ( pwmRequest.isAuthenticated() )
+        {
+            return ProcessStatus.Continue;
+        }
+
+        for ( final AuthenticationMethod authenticationMethod : AuthenticationMethod.values() )
+        {
+            if ( !IGNORED_AUTH_METHODS.contains( authenticationMethod ) )
+            {
+                PwmHttpFilterAuthenticationProvider filterAuthenticationProvider = null;
+                try
+                {
+                    final String className = authenticationMethod.getClassName();
+                    final Class clazz = Class.forName( className );
+                    final Object newInstance = clazz.newInstance();
+                    filterAuthenticationProvider = ( PwmHttpFilterAuthenticationProvider ) newInstance;
+                }
+                catch ( Exception e )
+                {
+                    LOGGER.trace( () -> "could not load authentication class '" + authenticationMethod + "', will ignore" );
+                    IGNORED_AUTH_METHODS.add( authenticationMethod );
+                }
+
+                if ( filterAuthenticationProvider != null )
+                {
+                    try
+                    {
+                        filterAuthenticationProvider.attemptAuthentication( pwmRequest );
+
+                        if ( pwmRequest.isAuthenticated() )
+                        {
+                            LOGGER.trace( pwmRequest, () -> "authentication provided by method " + authenticationMethod.name() );
+                        }
+
+                        if ( filterAuthenticationProvider.hasRedirectedResponse() )
+                        {
+                            LOGGER.trace( pwmRequest, () -> "authentication provider " + authenticationMethod.name()
+                                    + " has issued a redirect, halting authentication process" );
+                            return ProcessStatus.Halt;
+                        }
+
+                    }
+                    catch ( Exception e )
+                    {
+                        final ErrorInformation errorInformation;
+                        if ( e instanceof PwmException )
+                        {
+                            final String errorMsg = "error during " + authenticationMethod + " authentication attempt: " + e.getMessage();
+                            errorInformation = new ErrorInformation( ( ( PwmException ) e ).getError(), errorMsg );
+                        }
+                        else
+                        {
+                            errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, e.getMessage() );
+
+                        }
+                        LOGGER.error( pwmRequest, errorInformation );
+                        pwmRequest.respondWithError( errorInformation );
+                        return ProcessStatus.Halt;
+                    }
+                }
+            }
+        }
+        return ProcessStatus.Continue;
+    }
+
+    public static void handleAuthenticationCookie( final PwmRequest pwmRequest ) throws PwmUnrecoverableException
+    {
+        if ( !pwmRequest.isAuthenticated() || pwmRequest.getPwmSession().getLoginInfoBean().getType() != AuthenticationType.AUTHENTICATED )
+        {
+            return;
+        }
+
+        if ( pwmRequest.getPwmSession().getLoginInfoBean().isLoginFlag( LoginInfoBean.LoginFlag.authRecordSet ) )
+        {
+            return;
+        }
+
+        pwmRequest.getPwmSession().getLoginInfoBean().setFlag( LoginInfoBean.LoginFlag.authRecordSet );
+
+        final String cookieName = pwmRequest.getConfig().readAppProperty( AppProperty.HTTP_COOKIE_AUTHRECORD_NAME );
+        if ( cookieName == null || cookieName.isEmpty() )
+        {
+            LOGGER.debug( pwmRequest, () -> "skipping auth record cookie set, cookie name parameter is blank" );
+            return;
+        }
+
+        final int cookieAgeSeconds = Integer.parseInt( pwmRequest.getConfig().readAppProperty( AppProperty.HTTP_COOKIE_AUTHRECORD_AGE ) );
+        if ( cookieAgeSeconds < 1 )
+        {
+            LOGGER.debug( pwmRequest, () -> "skipping auth record cookie set, cookie age parameter is less than 1" );
+            return;
+        }
+
+        final Instant authTime = pwmRequest.getPwmSession().getLoginInfoBean().getAuthTime();
+        final String userGuid = pwmRequest.getPwmSession().getUserInfo().getUserGuid();
+        final HttpAuthRecord httpAuthRecord = new HttpAuthRecord( authTime, userGuid );
+
+        try
+        {
+            pwmRequest.getPwmResponse().writeEncryptedCookie( cookieName, httpAuthRecord, cookieAgeSeconds, PwmHttpResponseWrapper.CookiePath.Application );
+            LOGGER.debug( pwmRequest, () -> "wrote auth record cookie to user browser for use during forgotten password" );
+        }
+        catch ( PwmUnrecoverableException e )
+        {
+            LOGGER.error( pwmRequest, "error while setting authentication record cookie: " + e.getMessage() );
+        }
+    }
+
+}

+ 59 - 0
server/src/main/java/password/pwm/http/auth/OAuthFilterAuthenticationProvider.java

@@ -0,0 +1,59 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2018 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.http.auth;
+
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.PwmRequest;
+import password.pwm.http.servlet.oauth.OAuthMachine;
+import password.pwm.http.servlet.oauth.OAuthSettings;
+
+import java.io.IOException;
+
+public class OAuthFilterAuthenticationProvider implements PwmHttpFilterAuthenticationProvider
+{
+
+    private boolean redirected = false;
+
+    public void attemptAuthentication(
+            final PwmRequest pwmRequest
+    )
+            throws PwmUnrecoverableException, IOException
+    {
+        final OAuthSettings oauthSettings = OAuthSettings.forSSOAuthentication( pwmRequest.getConfig() );
+        if ( !oauthSettings.oAuthIsConfigured() )
+        {
+            return;
+        }
+
+        final String originalURL = pwmRequest.getURLwithQueryString();
+        final OAuthMachine oAuthMachine = new OAuthMachine( oauthSettings );
+        oAuthMachine.redirectUserToOAuthServer( pwmRequest, originalURL, null, null );
+        redirected = true;
+    }
+
+    @Override
+    public boolean hasRedirectedResponse( )
+    {
+        return redirected;
+    }
+}

+ 1 - 1
server/src/main/java/password/pwm/PwmHttpFilterAuthenticationProvider.java → server/src/main/java/password/pwm/http/auth/PwmHttpFilterAuthenticationProvider.java

@@ -20,7 +20,7 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-package password.pwm;
+package password.pwm.http.auth;
 
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;

+ 86 - 0
server/src/main/java/password/pwm/http/auth/SSOHeaderFilterAuthenticationProvider.java

@@ -0,0 +1,86 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2018 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.http.auth;
+
+import password.pwm.PwmApplication;
+import password.pwm.config.PwmSetting;
+import password.pwm.error.PwmOperationalException;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.PwmRequest;
+import password.pwm.http.PwmSession;
+import password.pwm.ldap.auth.AuthenticationType;
+import password.pwm.ldap.auth.PwmAuthenticationSource;
+import password.pwm.ldap.auth.SessionAuthenticator;
+import password.pwm.util.logging.PwmLogger;
+
+public class SSOHeaderFilterAuthenticationProvider implements PwmHttpFilterAuthenticationProvider
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( SSOHeaderFilterAuthenticationProvider.class );
+
+    @Override
+    public void attemptAuthentication(
+            final PwmRequest pwmRequest
+    )
+            throws PwmUnrecoverableException
+    {
+        {
+            final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
+            final PwmSession pwmSession = pwmRequest.getPwmSession();
+
+            final String headerName = pwmApplication.getConfig().readSettingAsString( PwmSetting.SSO_AUTH_HEADER_NAME );
+            if ( headerName == null || headerName.length() < 1 )
+            {
+                return;
+            }
+
+
+            final String headerValue = pwmRequest.readHeaderValueAsString( headerName );
+            if ( headerValue == null || headerValue.length() < 1 )
+            {
+                return;
+            }
+
+            LOGGER.debug( pwmRequest, () -> "SSO Authentication header present in request, will search for user value of '" + headerValue + "'" );
+            final SessionAuthenticator sessionAuthenticator = new SessionAuthenticator(
+                    pwmApplication,
+                    pwmSession,
+                    PwmAuthenticationSource.SSO_HEADER
+            );
+
+            try
+            {
+                sessionAuthenticator.authUserWithUnknownPassword( headerValue, AuthenticationType.AUTH_WITHOUT_PASSWORD );
+            }
+            catch ( PwmOperationalException e )
+            {
+                throw new PwmUnrecoverableException( e.getErrorInformation() );
+            }
+        }
+    }
+
+    @Override
+    public boolean hasRedirectedResponse( )
+    {
+        return false;
+    }
+}

+ 23 - 313
server/src/main/java/password/pwm/http/filter/AuthenticationFilter.java

@@ -22,25 +22,20 @@
 
 package password.pwm.http.filter;
 
-import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
-import password.pwm.PwmHttpFilterAuthenticationProvider;
 import password.pwm.bean.LoginInfoBean;
-import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
-import password.pwm.error.PwmException;
-import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.ProcessStatus;
-import password.pwm.http.PwmHttpResponseWrapper;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.PwmURL;
+import password.pwm.http.auth.HttpAuthenticationUtilities;
 import password.pwm.http.bean.ChangePasswordBean;
 import password.pwm.http.servlet.LoginServlet;
 import password.pwm.http.servlet.PwmServletDefinition;
@@ -50,11 +45,6 @@ import password.pwm.i18n.Display;
 import password.pwm.ldap.PasswordChangeProgressChecker;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.auth.AuthenticationType;
-import password.pwm.ldap.auth.PwmAuthenticationSource;
-import password.pwm.ldap.auth.SessionAuthenticator;
-import password.pwm.ldap.search.UserSearchEngine;
-import password.pwm.svc.stats.Statistic;
-import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.util.BasicAuthInfo;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.logging.PwmLogger;
@@ -62,10 +52,6 @@ import password.pwm.util.logging.PwmLogger;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import java.io.IOException;
-import java.io.Serializable;
-import java.time.Instant;
-import java.util.HashSet;
-import java.util.Set;
 
 /**
  * Authentication servlet filter.  This filter wraps all servlet requests and requests direct to *.jsp
@@ -77,7 +63,6 @@ import java.util.Set;
  */
 public class AuthenticationFilter extends AbstractPwmFilter
 {
-
     private static final PwmLogger LOGGER = PwmLogger.getLogger( AuthenticationFilter.class.getName() );
 
     public void processFilter(
@@ -193,81 +178,38 @@ public class AuthenticationFilter extends AbstractPwmFilter
                 return;
             }
         }
-        handleAuthenticationCookie( pwmRequest );
-
-        if ( forceRequiredRedirects( pwmRequest ) == ProcessStatus.Halt )
-        {
-            return;
-        }
-
-        // user session is authed, and session and auth header match, so forward request on.
-        chain.doFilter();
-    }
-
-    private static void handleAuthenticationCookie( final PwmRequest pwmRequest ) throws PwmUnrecoverableException
-    {
-        if ( !pwmRequest.isAuthenticated() || pwmRequest.getPwmSession().getLoginInfoBean().getType() != AuthenticationType.AUTHENTICATED )
-        {
-            return;
-        }
 
-        if ( pwmRequest.getPwmSession().getLoginInfoBean().isLoginFlag( LoginInfoBean.LoginFlag.authRecordSet ) )
-        {
-            return;
-        }
+        HttpAuthenticationUtilities.handleAuthenticationCookie( pwmRequest );
 
-        pwmRequest.getPwmSession().getLoginInfoBean().setFlag( LoginInfoBean.LoginFlag.authRecordSet );
-
-        final String cookieName = pwmRequest.getConfig().readAppProperty( AppProperty.HTTP_COOKIE_AUTHRECORD_NAME );
-        if ( cookieName == null || cookieName.isEmpty() )
+        if ( forceRequiredRedirects( pwmRequest ) == ProcessStatus.Halt )
         {
-            LOGGER.debug( pwmRequest, () -> "skipping auth record cookie set, cookie name parameter is blank" );
             return;
         }
 
-        final int cookieAgeSeconds = Integer.parseInt( pwmRequest.getConfig().readAppProperty( AppProperty.HTTP_COOKIE_AUTHRECORD_AGE ) );
-        if ( cookieAgeSeconds < 1 )
+        if ( pwmSession.getSessionManager().isAuthenticatedWithoutPasswordAndBind() )
         {
-            LOGGER.debug( pwmRequest, () -> "skipping auth record cookie set, cookie age parameter is less than 1" );
-            return;
+            final PwmServletDefinition pwmServletDefinition = pwmRequest.getURL().forServletDefinition();
+            if ( pwmServletDefinition != null
+                    && pwmServletDefinition.getFlags().contains( PwmServletDefinition.Flag.RequiresUserPasswordAndBind ) )
+            {
+                try
+                {
+                    LOGGER.debug( pwmSession, () -> "user is authenticated without a password, but module " + pwmServletDefinition.name()
+                            +  " requires user connection, redirecting to login page" );
+                    LoginServlet.redirectToLoginServlet( pwmRequest );
+                    return;
+                }
+                catch ( Throwable e1 )
+                {
+                    LOGGER.error( "error while marking pre-login url:" + e1.getMessage() );
+                }
+            }
         }
 
-        final Instant authTime = pwmRequest.getPwmSession().getLoginInfoBean().getAuthTime();
-        final String userGuid = pwmRequest.getPwmSession().getUserInfo().getUserGuid();
-        final AuthRecord authRecord = new AuthRecord( authTime, userGuid );
-
-        try
-        {
-            pwmRequest.getPwmResponse().writeEncryptedCookie( cookieName, authRecord, cookieAgeSeconds, PwmHttpResponseWrapper.CookiePath.Application );
-            LOGGER.debug( pwmRequest, () -> "wrote auth record cookie to user browser for use during forgotten password" );
-        }
-        catch ( PwmUnrecoverableException e )
-        {
-            LOGGER.error( pwmRequest, "error while setting authentication record cookie: " + e.getMessage() );
-        }
+        // user session is authed, and session and auth header match, so forward request on.
+        chain.doFilter();
     }
 
-    public static class AuthRecord implements Serializable
-    {
-        private Instant date;
-        private String guid;
-
-        public AuthRecord( final Instant date, final String guid )
-        {
-            this.date = date;
-            this.guid = guid;
-        }
-
-        public Instant getDate( )
-        {
-            return date;
-        }
-
-        public String getGuid( )
-        {
-            return guid;
-        }
-    }
 
     private void processUnAuthenticatedSession(
             final PwmRequest pwmRequest,
@@ -282,7 +224,7 @@ public class AuthenticationFilter extends AbstractPwmFilter
         final boolean bypassSso = pwmRequest.getPwmSession().getLoginInfoBean().isLoginFlag( LoginInfoBean.LoginFlag.noSso );
         if ( !bypassSso && pwmRequest.getPwmApplication().getApplicationMode() == PwmApplicationMode.RUNNING )
         {
-            final ProcessStatus authenticationProcessStatus = attemptAuthenticationMethods( pwmRequest );
+            final ProcessStatus authenticationProcessStatus = HttpAuthenticationUtilities.attemptAuthenticationMethods( pwmRequest );
             if ( authenticationProcessStatus == ProcessStatus.Halt )
             {
                 return;
@@ -335,76 +277,6 @@ public class AuthenticationFilter extends AbstractPwmFilter
         LoginServlet.redirectToLoginServlet( pwmRequest );
     }
 
-    private static final Set<AuthenticationMethod> IGNORED_AUTH_METHODS = new HashSet<>();
-
-    private static ProcessStatus attemptAuthenticationMethods( final PwmRequest pwmRequest ) throws IOException, ServletException
-    {
-        if ( pwmRequest.isAuthenticated() )
-        {
-            return ProcessStatus.Continue;
-        }
-
-        for ( final AuthenticationMethod authenticationMethod : AuthenticationMethod.values() )
-        {
-            if ( !IGNORED_AUTH_METHODS.contains( authenticationMethod ) )
-            {
-                PwmHttpFilterAuthenticationProvider filterAuthenticationProvider = null;
-                try
-                {
-                    final String className = authenticationMethod.getClassName();
-                    final Class clazz = Class.forName( className );
-                    final Object newInstance = clazz.newInstance();
-                    filterAuthenticationProvider = ( PwmHttpFilterAuthenticationProvider ) newInstance;
-                }
-                catch ( Exception e )
-                {
-                    LOGGER.trace( () -> "could not load authentication class '" + authenticationMethod + "', will ignore" );
-                    IGNORED_AUTH_METHODS.add( authenticationMethod );
-                }
-
-                if ( filterAuthenticationProvider != null )
-                {
-                    try
-                    {
-                        filterAuthenticationProvider.attemptAuthentication( pwmRequest );
-
-                        if ( pwmRequest.isAuthenticated() )
-                        {
-                            LOGGER.trace( pwmRequest, () -> "authentication provided by method " + authenticationMethod.name() );
-                        }
-
-                        if ( filterAuthenticationProvider.hasRedirectedResponse() )
-                        {
-                            LOGGER.trace( pwmRequest, () -> "authentication provider " + authenticationMethod.name()
-                                    + " has issued a redirect, halting authentication process" );
-                            return ProcessStatus.Halt;
-                        }
-
-                    }
-                    catch ( Exception e )
-                    {
-                        final ErrorInformation errorInformation;
-                        if ( e instanceof PwmException )
-                        {
-                            final String errorMsg = "error during " + authenticationMethod + " authentication attempt: " + e.getMessage();
-                            errorInformation = new ErrorInformation( ( ( PwmException ) e ).getError(), errorMsg );
-                        }
-                        else
-                        {
-                            errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, e.getMessage() );
-
-                        }
-                        LOGGER.error( pwmRequest, errorInformation );
-                        pwmRequest.respondWithError( errorInformation );
-                        return ProcessStatus.Halt;
-                    }
-                }
-            }
-        }
-        return ProcessStatus.Continue;
-    }
-
-
     public static ProcessStatus forceRequiredRedirects(
             final PwmRequest pwmRequest
     )
@@ -499,7 +371,6 @@ public class AuthenticationFilter extends AbstractPwmFilter
             }
         }
 
-
         if ( !pwmURL.isChangePasswordURL() )
         {
             if ( userInfo.isRequiresNewPassword() && !loginInfoBean.isLoginFlag( LoginInfoBean.LoginFlag.skipNewPw ) )
@@ -522,166 +393,5 @@ public class AuthenticationFilter extends AbstractPwmFilter
 
         return ProcessStatus.Continue;
     }
-
-    enum AuthenticationMethod
-    {
-        BASIC_AUTH( BasicFilterAuthenticationProvider.class.getName() ),
-        SSO_AUTH_HEADER( SSOHeaderFilterAuthenticationProvider.class.getName() ),
-        CAS( "password.pwm.util.CASFilterAuthenticationProvider" ),
-        OAUTH( OAuthFilterAuthenticationProvider.class.getName() );
-
-        private final String className;
-
-        AuthenticationMethod( final String className )
-        {
-            this.className = className;
-        }
-
-        public String getClassName( )
-        {
-            return className;
-        }
-    }
-
-    public static class BasicFilterAuthenticationProvider implements PwmHttpFilterAuthenticationProvider
-    {
-
-        @Override
-        public void attemptAuthentication(
-                final PwmRequest pwmRequest
-        )
-                throws PwmUnrecoverableException
-        {
-            if ( !pwmRequest.getConfig().readSettingAsBoolean( PwmSetting.BASIC_AUTH_ENABLED ) )
-            {
-                return;
-            }
-
-            if ( pwmRequest.isAuthenticated() )
-            {
-                return;
-            }
-
-            final BasicAuthInfo basicAuthInfo = BasicAuthInfo.parseAuthHeader( pwmRequest.getPwmApplication(), pwmRequest );
-            if ( basicAuthInfo == null )
-            {
-                return;
-            }
-
-            try
-            {
-                final PwmSession pwmSession = pwmRequest.getPwmSession();
-                final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-
-                //user isn't already authenticated and has an auth header, so try to auth them.
-                LOGGER.debug( pwmSession, () -> "attempting to authenticate user using basic auth header (username=" + basicAuthInfo.getUsername() + ")" );
-                final SessionAuthenticator sessionAuthenticator = new SessionAuthenticator(
-                        pwmApplication,
-                        pwmSession,
-                        PwmAuthenticationSource.BASIC_AUTH
-                );
-                final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
-                final UserIdentity userIdentity = userSearchEngine.resolveUsername( basicAuthInfo.getUsername(), null, null, pwmSession.getLabel() );
-                sessionAuthenticator.authenticateUser( userIdentity, basicAuthInfo.getPassword() );
-                pwmSession.getLoginInfoBean().setBasicAuth( basicAuthInfo );
-
-            }
-            catch ( PwmException e )
-            {
-                if ( e.getError() == PwmError.ERROR_DIRECTORY_UNAVAILABLE )
-                {
-                    StatisticsManager.incrementStat( pwmRequest, Statistic.LDAP_UNAVAILABLE_COUNT );
-                }
-                throw new PwmUnrecoverableException( e.getError() );
-            }
-        }
-
-        @Override
-        public boolean hasRedirectedResponse( )
-        {
-            return false;
-        }
-    }
-
-    static class SSOHeaderFilterAuthenticationProvider implements PwmHttpFilterAuthenticationProvider
-    {
-
-        @Override
-        public void attemptAuthentication(
-                final PwmRequest pwmRequest
-        )
-                throws PwmUnrecoverableException
-        {
-            {
-                final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-                final PwmSession pwmSession = pwmRequest.getPwmSession();
-
-                final String headerName = pwmApplication.getConfig().readSettingAsString( PwmSetting.SSO_AUTH_HEADER_NAME );
-                if ( headerName == null || headerName.length() < 1 )
-                {
-                    return;
-                }
-
-
-                final String headerValue = pwmRequest.readHeaderValueAsString( headerName );
-                if ( headerValue == null || headerValue.length() < 1 )
-                {
-                    return;
-                }
-
-                LOGGER.debug( pwmRequest, () -> "SSO Authentication header present in request, will search for user value of '" + headerValue + "'" );
-                final SessionAuthenticator sessionAuthenticator = new SessionAuthenticator(
-                        pwmApplication,
-                        pwmSession,
-                        PwmAuthenticationSource.SSO_HEADER
-                );
-
-                try
-                {
-                    sessionAuthenticator.authUserWithUnknownPassword( headerValue, AuthenticationType.AUTH_WITHOUT_PASSWORD );
-                }
-                catch ( PwmOperationalException e )
-                {
-                    throw new PwmUnrecoverableException( e.getErrorInformation() );
-                }
-            }
-        }
-
-        @Override
-        public boolean hasRedirectedResponse( )
-        {
-            return false;
-        }
-    }
-
-
-    static class OAuthFilterAuthenticationProvider implements PwmHttpFilterAuthenticationProvider
-    {
-
-        private boolean redirected = false;
-
-        public void attemptAuthentication(
-                final PwmRequest pwmRequest
-        )
-                throws PwmUnrecoverableException, IOException
-        {
-            final OAuthSettings oauthSettings = OAuthSettings.forSSOAuthentication( pwmRequest.getConfig() );
-            if ( !oauthSettings.oAuthIsConfigured() )
-            {
-                return;
-            }
-
-            final String originalURL = pwmRequest.getURLwithQueryString();
-            final OAuthMachine oAuthMachine = new OAuthMachine( oauthSettings );
-            oAuthMachine.redirectUserToOAuthServer( pwmRequest, originalURL, null, null );
-            redirected = true;
-        }
-
-        @Override
-        public boolean hasRedirectedResponse( )
-        {
-            return redirected;
-        }
-    }
 }
 

+ 13 - 4
server/src/main/java/password/pwm/http/servlet/LoginServlet.java

@@ -25,6 +25,7 @@ package password.pwm.http.servlet;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import password.pwm.PwmConstants;
 import password.pwm.bean.UserIdentity;
+import password.pwm.config.PwmSetting;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
@@ -55,8 +56,8 @@ import java.util.Map;
 
 /**
  * User interaction servlet for form-based authentication.   Depending on how PWM is deployed,
- * users may or may not ever visit this servlet.   Generally, if PWM is behind iChain, or some
- * other SSO enabler using HTTP BASIC authentication, this form will not be invoked.
+ * users may or may not ever visit this servlet.   If using some type of SSO method
+ * the login form will not be invoked.
  *
  * @author Jason D. Rivard
  */
@@ -105,15 +106,23 @@ public class LoginServlet extends ControlledPwmServlet
     }
 
     @Override
-    protected void nextStep( final PwmRequest pwmRequest ) throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException
+    protected void nextStep( final PwmRequest pwmRequest ) throws PwmUnrecoverableException, IOException, ServletException
     {
         final boolean passwordOnly = passwordOnly( pwmRequest );
         forwardToJSP( pwmRequest, passwordOnly );
     }
 
     @Override
-    public ProcessStatus preProcessCheck( final PwmRequest pwmRequest ) throws PwmUnrecoverableException, IOException, ServletException
+    public ProcessStatus preProcessCheck( final PwmRequest pwmRequest ) throws PwmUnrecoverableException, IOException
     {
+        if ( pwmRequest.isAuthenticated() && !passwordOnly( pwmRequest ) )
+        {
+            final String redirectURL = pwmRequest.getContextPath()
+                    + pwmRequest.getConfig().readSettingAsString( PwmSetting.URL_INTRO );
+            LOGGER.debug( pwmRequest, () -> "user is already authenticated, so redirecting user to intro url: " + redirectURL );
+            pwmRequest.sendRedirect( redirectURL );
+            return ProcessStatus.Halt;
+        }
         return ProcessStatus.Continue;
     }
 

+ 28 - 9
server/src/main/java/password/pwm/http/servlet/PwmServletDefinition.java

@@ -61,6 +61,9 @@ import password.pwm.http.servlet.updateprofile.UpdateProfileServlet;
 import javax.servlet.annotation.WebServlet;
 import java.lang.annotation.Annotation;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
 
 public enum PwmServletDefinition
 {
@@ -70,18 +73,18 @@ public enum PwmServletDefinition
     PublicCommand( PublicCommandServlet.class, null ),
     PublicPeopleSearch( PublicPeopleSearchServlet.class, null ),
     PublicChangePassword( PublicChangePasswordServlet.class, ChangePasswordBean.class ),
-    //Resource(password.pwm.http.servlet.ResourceFileServlet.class),
+    Resource( password.pwm.http.servlet.resource.ResourceFileServlet.class, null ),
 
     AccountInformation( AccountInformationServlet.class, null ),
-    PrivateChangePassword( PrivateChangePasswordServlet.class, ChangePasswordBean.class ),
-    SetupResponses( password.pwm.http.servlet.SetupResponsesServlet.class, SetupResponsesBean.class ),
-    UpdateProfile( UpdateProfileServlet.class, UpdateProfileBean.class ),
-    SetupOtp( password.pwm.http.servlet.SetupOtpServlet.class, SetupOtpBean.class ),
+    PrivateChangePassword( PrivateChangePasswordServlet.class, ChangePasswordBean.class, Flag.RequiresUserPasswordAndBind ),
+    SetupResponses( password.pwm.http.servlet.SetupResponsesServlet.class, SetupResponsesBean.class, Flag.RequiresUserPasswordAndBind ),
+    UpdateProfile( UpdateProfileServlet.class, UpdateProfileBean.class, Flag.RequiresUserPasswordAndBind ),
+    SetupOtp( password.pwm.http.servlet.SetupOtpServlet.class, SetupOtpBean.class, Flag.RequiresUserPasswordAndBind ),
     Helpdesk( password.pwm.http.servlet.helpdesk.HelpdeskServlet.class, null ),
     Shortcuts( password.pwm.http.servlet.ShortcutServlet.class, ShortcutsBean.class ),
     PrivateCommand( PrivateCommandServlet.class, null ),
     PrivatePeopleSearch( PrivatePeopleSearchServlet.class, null ),
-    GuestRegistration( password.pwm.http.servlet.GuestRegistrationServlet.class, null ),
+    GuestRegistration( password.pwm.http.servlet.GuestRegistrationServlet.class, null, Flag.RequiresUserPasswordAndBind ),
     SelfDelete( DeleteAccountServlet.class, DeleteAccountBean.class ),
 
     ClientApi( ClientApiServlet.class, null ),
@@ -102,11 +105,24 @@ public enum PwmServletDefinition
     private final String servletUrl;
     private final Class<? extends PwmServlet> pwmServletClass;
     private final Class<? extends PwmSessionBean> pwmSessionBeanClass;
+    private final Collection<Flag> flags;
 
-    PwmServletDefinition( final Class<? extends PwmServlet> pwmServletClass, final Class<? extends PwmSessionBean> pwmSessionBeanClass )
+    public enum Flag
+    {
+        RequiresUserPasswordAndBind,
+    }
+
+    PwmServletDefinition(
+            final Class<? extends PwmServlet> pwmServletClass,
+            final Class<? extends PwmSessionBean> pwmSessionBeanClass,
+            final Flag... flags
+    )
     {
         this.pwmServletClass = pwmServletClass;
         this.pwmSessionBeanClass = pwmSessionBeanClass;
+        final EnumSet flagSet = EnumSet.noneOf( Flag.class );
+        Collections.addAll( flagSet, flags );
+        this.flags = Collections.unmodifiableSet( flagSet );
 
         try
         {
@@ -119,7 +135,7 @@ public enum PwmServletDefinition
 
         final String firstPattern = patterns[ 0 ];
         final int lastSlash = firstPattern.lastIndexOf( "/" );
-        servletUrl = firstPattern.substring( lastSlash + 1, firstPattern.length() );
+        servletUrl = firstPattern.substring( lastSlash + 1 );
     }
 
     public String[] urlPatterns( )
@@ -160,5 +176,8 @@ public enum PwmServletDefinition
         throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, "missing WebServlet annotation for class " + this.getClass().getName() ) );
     }
 
-
+    public Collection<Flag> getFlags()
+    {
+        return flags;
+    }
 }

+ 4 - 4
server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java

@@ -53,8 +53,8 @@ import password.pwm.error.PwmException;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
+import password.pwm.http.auth.HttpAuthRecord;
 import password.pwm.http.bean.ForgottenPasswordBean;
-import password.pwm.http.filter.AuthenticationFilter;
 import password.pwm.i18n.Message;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
@@ -254,10 +254,10 @@ public class ForgottenPasswordUtil
                 return false;
             }
 
-            final AuthenticationFilter.AuthRecord authRecord = pwmRequest.readEncryptedCookie( cookieName, AuthenticationFilter.AuthRecord.class );
-            if ( authRecord != null )
+            final HttpAuthRecord httpAuthRecord = pwmRequest.readEncryptedCookie( cookieName, HttpAuthRecord.class );
+            if ( httpAuthRecord != null )
             {
-                if ( authRecord.getGuid() != null && !authRecord.getGuid().isEmpty() && authRecord.getGuid().equals( userGuid ) )
+                if ( httpAuthRecord.getGuid() != null && !httpAuthRecord.getGuid().isEmpty() && httpAuthRecord.getGuid().equals( userGuid ) )
                 {
                     LOGGER.debug( pwmRequest, () -> "auth record cookie validated" );
                     return true;

+ 13 - 34
server/src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java

@@ -94,31 +94,24 @@ public class OAuthConsumerServlet extends AbstractPwmServlet
         );
 
         // make sure it's okay to be processing this request.
-        switch ( oAuthUseCaseCase )
+        // for non-auth requests its okay to continue
+        if ( oAuthUseCaseCase == OAuthUseCase.Authentication )
         {
-            case Authentication:
+            if ( !userIsAuthenticated && !pwmSession.getSessionStateBean().isOauthInProgress() )
             {
-                if ( !userIsAuthenticated && !pwmSession.getSessionStateBean().isOauthInProgress() )
+                if ( oAuthRequestState.isPresent() )
                 {
-                    if ( oAuthRequestState.isPresent() )
-                    {
-                        final String nextUrl = oAuthRequestState.get().getOAuthState().getNextUrl();
-                        LOGGER.debug( pwmSession, () -> "received unrecognized oauth response, ignoring authcode and redirecting to embedded next url: " + nextUrl );
-                        pwmRequest.sendRedirect( nextUrl );
-                        return;
-                    }
-                    final String errorMsg = "oauth consumer reached, but oauth authentication has not yet been initiated.";
-                    final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_OAUTH_ERROR, errorMsg );
-                    pwmRequest.respondWithError( errorInformation );
-                    LOGGER.error( pwmSession, errorMsg );
+                    final String nextUrl = oAuthRequestState.get().getOAuthState().getNextUrl();
+                    LOGGER.debug( pwmSession, () -> "received unrecognized oauth response, ignoring authcode and redirecting to embedded next url: " + nextUrl );
+                    pwmRequest.sendRedirect( nextUrl );
                     return;
                 }
+                final String errorMsg = "oauth consumer reached, but oauth authentication has not yet been initiated.";
+                final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_OAUTH_ERROR, errorMsg );
+                pwmRequest.respondWithError( errorInformation );
+                LOGGER.error( pwmSession, errorMsg );
+                return;
             }
-            break;
-
-            default:
-                // for non-auth requests its okay to continue
-                break;
         }
 
         // check if there is an "error" on the request sent from the oauth server., if there is then halt.
@@ -245,21 +238,7 @@ public class OAuthConsumerServlet extends AbstractPwmServlet
         }
         */
 
-        final String oauthSuppliedUsername;
-        {
-            final String getAttributeResponseBodyStr = oAuthMachine.makeOAuthGetAttributeRequest( pwmRequest, resolveResults.getAccessToken() );
-            final Map<String, String> getAttributeResultValues = JsonUtil.deserializeStringMap( getAttributeResponseBodyStr );
-            oauthSuppliedUsername = getAttributeResultValues.get( oAuthSettings.getDnAttributeName() );
-            if ( oauthSuppliedUsername == null || oauthSuppliedUsername.isEmpty() )
-            {
-                final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_OAUTH_ERROR, "OAuth server did not respond with an username attribute value" );
-                LOGGER.error( pwmRequest, errorInformation );
-                pwmRequest.respondWithError( errorInformation );
-                return;
-            }
-        }
-
-        LOGGER.debug( pwmSession, () -> "received user login id value from OAuth server: " + oauthSuppliedUsername );
+        final String oauthSuppliedUsername = oAuthMachine.makeOAuthGetUserInfoRequest( pwmRequest, resolveResults.getAccessToken() );
 
         if ( oAuthUseCaseCase == OAuthUseCase.ForgottenPassword )
         {

+ 31 - 9
server/src/main/java/password/pwm/http/servlet/oauth/OAuthMachine.java

@@ -117,8 +117,11 @@ public class OAuthMachine
         urlParams.put( config.readAppProperty( AppProperty.HTTP_PARAM_OAUTH_RESPONSE_TYPE ), code );
         urlParams.put( config.readAppProperty( AppProperty.HTTP_PARAM_OAUTH_STATE ), state );
         urlParams.put( config.readAppProperty( AppProperty.HTTP_PARAM_OAUTH_REDIRECT_URI ), redirectUri );
-        urlParams.put( "resourceServer", "value" );
-        urlParams.put( "scope", "openid" );
+
+        if ( !StringUtil.isEmpty( settings.getScope() ) )
+        {
+            urlParams.put( config.readAppProperty( AppProperty.HTTP_PARAM_OAUTH_SCOPE ), settings.getScope() );
+        }
 
         if ( userIdentity != null )
         {
@@ -164,7 +167,7 @@ public class OAuthMachine
         requestParams.put( config.readAppProperty( AppProperty.HTTP_PARAM_OAUTH_CLIENT_ID ), clientID );
         requestParams.put( "client_secret", settings.getSecret().getStringValue() );
 
-        final PwmHttpClientResponse restResults = makeHttpRequest( pwmRequest, "oauth code resolver", settings, requestUrl, requestParams );
+        final PwmHttpClientResponse restResults = makeHttpRequest( pwmRequest, "oauth code resolver", settings, requestUrl, requestParams, null );
 
         return resolveResultsFromResponseBody( pwmRequest.getSessionLabel(), pwmRequest.getConfig(), restResults.getBody() );
     }
@@ -210,12 +213,12 @@ public class OAuthMachine
         requestParams.put( config.readAppProperty( AppProperty.HTTP_PARAM_OAUTH_REFRESH_TOKEN ), refreshCode );
         requestParams.put( config.readAppProperty( AppProperty.HTTP_PARAM_OAUTH_GRANT_TYPE ), grantType );
 
-        final PwmHttpClientResponse restResults = makeHttpRequest( pwmRequest, "OAuth refresh resolver", settings, requestUrl, requestParams );
+        final PwmHttpClientResponse restResults = makeHttpRequest( pwmRequest, "OAuth refresh resolver", settings, requestUrl, requestParams, null );
 
         return resolveResultsFromResponseBody( pwmRequest.getSessionLabel(), pwmRequest.getConfig(), restResults.getBody() );
     }
 
-    String makeOAuthGetAttributeRequest(
+    String makeOAuthGetUserInfoRequest(
             final PwmRequest pwmRequest,
             final String accessToken
     )
@@ -227,9 +230,27 @@ public class OAuthMachine
         requestParams.put( config.readAppProperty( AppProperty.HTTP_PARAM_OAUTH_ACCESS_TOKEN ), accessToken );
         requestParams.put( config.readAppProperty( AppProperty.HTTP_PARAM_OAUTH_ATTRIBUTES ), settings.getDnAttributeName() );
 
-        final PwmHttpClientResponse restResults = makeHttpRequest( pwmRequest, "OAuth getattribute", settings, requestUrl, requestParams, accessToken );
+        final PwmHttpClientResponse restResults = makeHttpRequest( pwmRequest, "OAuth userinfo", settings, requestUrl, requestParams, accessToken );
+
+        final String resultBody = restResults.getBody();
+        final Map<String, String> getAttributeResultValues = JsonUtil.deserializeStringMap( resultBody );
+
+        LOGGER.debug( pwmRequest, () -> "received attribute values from OAuth IdP for attributes: "
+                + StringUtil.collectionToString( getAttributeResultValues.keySet() ) );
+
+        final String oauthSuppliedUsername = getAttributeResultValues.get( settings.getDnAttributeName() );
 
-        return restResults.getBody();
+        if ( StringUtil.isEmpty( oauthSuppliedUsername ) )
+        {
+            final String msg = "OAuth server did not respond with an username value for configured attribute '" + settings.getDnAttributeName() + "'";
+            final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_OAUTH_ERROR, msg );
+            LOGGER.error( pwmRequest, errorInformation );
+            throw new PwmUnrecoverableException( errorInformation );
+        }
+
+        LOGGER.debug( pwmRequest, () -> "received user login id value from OAuth server: " + oauthSuppliedUsername );
+
+        return oauthSuppliedUsername;
     }
 
     private static PwmHttpClientResponse makeHttpRequest(
@@ -255,7 +276,8 @@ public class OAuthMachine
             }
             else
             {
-
+                headers.put( HttpHeader.Authorization.getHttpName(),
+                        "Bearer " + accessToken );
             }
             headers.put( HttpHeader.ContentType.getHttpName(), HttpContentType.form.getHeaderValue() );
 
@@ -451,7 +473,7 @@ public class OAuthMachine
         }
 
         LOGGER.debug( pwmRequest, () -> "preparing to send username to OAuth /sign endpoint for future injection to /grant redirect" );
-        final PwmHttpClientResponse restResults = makeHttpRequest( pwmRequest, "OAuth pre-inject username signing service", settings, signUrl, requestPayload );
+        final PwmHttpClientResponse restResults = makeHttpRequest( pwmRequest, "OAuth pre-inject username signing service", settings, signUrl, requestPayload, null );
 
         final String resultBody = restResults.getBody();
         final Map<String, String> resultBodyMap = JsonUtil.deserializeStringMap( resultBody );

+ 2 - 0
server/src/main/java/password/pwm/http/servlet/oauth/OAuthSettings.java

@@ -40,6 +40,7 @@ public class OAuthSettings implements Serializable
     private String loginURL;
     private String codeResolveUrl;
     private String attributesUrl;
+    private String scope;
     private String clientID;
     private PasswordData secret;
     private String dnAttributeName;
@@ -67,6 +68,7 @@ public class OAuthSettings implements Serializable
                 .secret( config.readSettingAsPassword( PwmSetting.OAUTH_ID_SECRET ) )
                 .dnAttributeName( config.readSettingAsString( PwmSetting.OAUTH_ID_DN_ATTRIBUTE_NAME ) )
                 .certificates( config.readSettingAsCertificate( PwmSetting.OAUTH_ID_CERTIFICATE ) )
+                .scope( config.readSettingAsString( PwmSetting.OAUTH_ID_SCOPE ) )
                 .use( OAuthUseCase.Authentication )
                 .build();
     }

+ 9 - 1
server/src/main/java/password/pwm/http/state/CryptoCookieLoginImpl.java

@@ -128,6 +128,13 @@ class CryptoCookieLoginImpl implements SessionLoginProvider
 
                 checkIfLoginCookieIsForeign( pwmRequest, remoteLoginCookie );
 
+                if ( remoteLoginCookie.getType() == AuthenticationType.AUTH_WITHOUT_PASSWORD && remoteLoginCookie.getUserCurrentPassword() == null )
+                {
+                    LOGGER.debug( () -> "remote session has authType " + AuthenticationType.AUTH_WITHOUT_PASSWORD.name()
+                            + " and does not contain password, thus ignoring authentication so SSO process can repeat" );
+                    return;
+                }
+
                 importRemoteCookie( pwmRequest, remoteLoginCookie );
             }
             catch ( Exception e )
@@ -144,7 +151,8 @@ class CryptoCookieLoginImpl implements SessionLoginProvider
     private static void importRemoteCookie(
             final PwmRequest pwmRequest,
             final LoginInfoBean remoteLoginCookie
-    ) throws PwmUnrecoverableException
+    )
+            throws PwmUnrecoverableException
     {
         if ( remoteLoginCookie == null )
         {

+ 2 - 26
server/src/main/java/password/pwm/ldap/auth/AuthenticationResult.java

@@ -23,37 +23,13 @@
 package password.pwm.ldap.auth;
 
 import com.novell.ldapchai.provider.ChaiProvider;
+import lombok.Value;
 import password.pwm.util.PasswordData;
 
+@Value
 public class AuthenticationResult
 {
     private final ChaiProvider userProvider;
     private final AuthenticationType authenticationType;
     private final PasswordData userPassword;
-
-    public AuthenticationResult(
-            final ChaiProvider userProvider,
-            final AuthenticationType authenticationType,
-            final PasswordData userPassword
-    )
-    {
-        this.userProvider = userProvider;
-        this.authenticationType = authenticationType;
-        this.userPassword = userPassword;
-    }
-
-    public ChaiProvider getUserProvider( )
-    {
-        return userProvider;
-    }
-
-    public AuthenticationType getAuthenticationType( )
-    {
-        return authenticationType;
-    }
-
-    public PasswordData getUserPassword( )
-    {
-        return userPassword;
-    }
 }

+ 98 - 54
server/src/main/java/password/pwm/ldap/auth/LDAPAuthenticationRequest.java

@@ -69,6 +69,7 @@ import java.time.Instant;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.function.Supplier;
 
 class LDAPAuthenticationRequest implements AuthenticationRequest
 {
@@ -123,7 +124,7 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
     {
         initialize();
 
-        log( PwmLogLevel.TRACE, "beginning authentication using unknown password procedure" );
+        log( PwmLogLevel.TRACE, () -> "beginning authentication using unknown password procedure" );
 
         PasswordData userPassword = null;
         final boolean configAlwaysUseProxy = pwmApplication.getConfig().readSettingAsBoolean( PwmSetting.AD_USE_PROXY_FOR_FORGOTTEN );
@@ -146,10 +147,12 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
                     strategy = AuthenticationStrategy.WRITE_THEN_BIND;
                 }
             }
-            if ( userPassword == null )
-            {
-                throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, "no available unknown-pw authentication method" ) );
-            }
+        }
+
+        if ( userPassword == null && requestedAuthType == AuthenticationType.AUTH_WITHOUT_PASSWORD )
+        {
+            log( PwmLogLevel.TRACE, () -> "unable to learn password or connect using proxy, thus authenticating user without a password" );
+            return authenticateUserWithoutPassword();
         }
 
         try
@@ -182,19 +185,31 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
         return authenticateUserImpl( password );
     }
 
-    private AuthenticationResult authenticateUserImpl(
-            final PasswordData password
-    )
-            throws ChaiUnavailableException, PwmUnrecoverableException, PwmOperationalException
+    private AuthenticationResult authenticateUserWithoutPassword() throws PwmUnrecoverableException
     {
-        if ( startTime == null )
+        if ( !Boolean.parseBoolean( pwmApplication.getConfig().readAppProperty( AppProperty.AUTH_ALLOW_SSO_WITH_UNKNOWN_PW ) ) )
         {
-            startTime = Instant.now();
+            log( PwmLogLevel.TRACE, () -> "AppProperty " + AppProperty.AUTH_ALLOW_SSO_WITH_UNKNOWN_PW + " is not true, thus prohibiting auth with unknown password" );
+            throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, "no available unknown-pw authentication method" ) );
         }
 
-        log( PwmLogLevel.DEBUG, "preparing to authenticate user using authenticationType=" + this.requestedAuthType + " using strategy " + this.strategy );
+        preAuthenticationChecks();
+
+        final AuthenticationResult authenticationResult = new AuthenticationResult(
+                null,
+                AuthenticationType.AUTH_WITHOUT_PASSWORD,
+                null
+        );
+
+        postAuthenticationSteps( authenticationResult, false );
+
+        return authenticationResult;
+    }
+
+    private void preAuthenticationChecks() throws PwmUnrecoverableException
+    {
+        log( PwmLogLevel.DEBUG, () -> "preparing to authenticate user using authenticationType=" + this.requestedAuthType + " using strategy " + this.strategy );
 
-        final StatisticsManager statisticsManager = pwmApplication.getStatisticsManager();
         final IntruderManager intruderManager = pwmApplication.getIntruderManager();
         intruderManager.convenience().checkUserIdentity( userIdentity );
         intruderManager.check( RecordType.ADDRESS, sessionLabel.getSrcAddress() );
@@ -202,6 +217,21 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
         // verify user is not account disabled
         AuthenticationUtility.checkIfUserEligibleToAuthentication( pwmApplication, userIdentity );
 
+    }
+
+    private AuthenticationResult authenticateUserImpl(
+            final PasswordData password
+    )
+            throws ChaiUnavailableException, PwmUnrecoverableException, PwmOperationalException
+    {
+        if ( startTime == null )
+        {
+            startTime = Instant.now();
+        }
+
+        preAuthenticationChecks();
+
+
         boolean allowBindAsUser = true;
         if ( strategy == AuthenticationStrategy.ADMIN_PROXY )
         {
@@ -225,8 +255,8 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
                     {
                         if ( pwmApplication.getConfig().readSettingAsBoolean( PwmSetting.AD_ALLOW_AUTH_REQUIRE_NEW_PWD ) )
                         {
-                            log( PwmLogLevel.INFO,
-                                    "auth bind failed, but will allow login due to 'must change password on next login AD error', error: "
+                            log( PwmLogLevel.DEBUG,
+                                    () -> "auth bind failed, but will allow login due to 'must change password on next login AD error', error: "
                                             + e.getErrorInformation().toDebugStr() );
                             allowBindAsUser = false;
                             permitAuthDespiteError = true;
@@ -237,8 +267,8 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
                         if ( pwmApplication.getConfig().readSettingAsBoolean(
                                 PwmSetting.ORACLE_DS_ALLOW_AUTH_REQUIRE_NEW_PWD ) )
                         {
-                            log( PwmLogLevel.INFO,
-                                    "auth bind failed, but will allow login due to 'pwdReset' user attribute, error: "
+                            log( PwmLogLevel.DEBUG,
+                                    () -> "auth bind failed, but will allow login due to 'pwdReset' user attribute, error: "
                                             + e.getErrorInformation().toDebugStr() );
                             allowBindAsUser = false;
                             permitAuthDespiteError = true;
@@ -256,8 +286,8 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
                             {
                                 throw e;
                             }
-                            log( PwmLogLevel.INFO,
-                                    "auth bind failed, but will allow login due to 'password expired AD error', error: " + e.getErrorInformation().toDebugStr() );
+                            log( PwmLogLevel.DEBUG,
+                                    () -> "auth bind failed, but will allow login due to 'password expired AD error', error: " + e.getErrorInformation().toDebugStr() );
                             allowBindAsUser = false;
                             permitAuthDespiteError = true;
                         }
@@ -267,17 +297,12 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
                 if ( !permitAuthDespiteError )
                 {
                     // auth failed, presumably due to wrong password.
-                    statisticsManager.incrementValue( Statistic.AUTHENTICATION_FAILURES );
+                    StatisticsManager.incrementStat( pwmApplication, Statistic.AUTHENTICATION_FAILURES );
                     throw e;
                 }
             }
         }
 
-        statisticsManager.incrementValue( Statistic.AUTHENTICATIONS );
-        statisticsManager.updateEps( EpsStatistic.AUTHENTICATION, 1 );
-        statisticsManager.updateAverageValue( Statistic.AVG_AUTHENTICATION_TIME,
-                TimeDuration.fromCurrent( startTime ).asMillis() );
-
         final AuthenticationType returnAuthType;
         if ( !allowBindAsUser )
         {
@@ -310,26 +335,45 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
         final ChaiProvider returnProvider = useProxy ? makeProxyProvider() : userProvider;
         final AuthenticationResult authenticationResult = new AuthenticationResult( returnProvider, returnAuthType, password );
 
-        final StringBuilder debugMsg = new StringBuilder();
-        debugMsg.append( "successful ldap authentication for " ).append( userIdentity );
-        debugMsg.append( " (" ).append( TimeDuration.fromCurrent( startTime ).asCompactString() ).append( ")" );
-        debugMsg.append( " type: " ).append( returnAuthType ).append( ", using strategy " ).append( strategy );
-        debugMsg.append( ", using proxy connection: " ).append( useProxy );
-        debugMsg.append( ", returning bind dn: " ).append( returnProvider == null ? "none" : returnProvider.getChaiConfiguration().getSetting( ChaiSetting.BIND_DN ) );
-        log( PwmLogLevel.INFO, debugMsg );
+        postAuthenticationSteps( authenticationResult, useProxy );
+
+        return authenticationResult;
+    }
+
+    private void postAuthenticationSteps(
+            final AuthenticationResult authenticationResult,
+            final boolean usingProxy
+    )
+            throws PwmUnrecoverableException
+    {
+        final StatisticsManager statisticsManager = pwmApplication.getStatisticsManager();
+        statisticsManager.incrementValue( Statistic.AUTHENTICATIONS );
+        statisticsManager.updateEps( EpsStatistic.AUTHENTICATION, 1 );
+        statisticsManager.updateAverageValue( Statistic.AVG_AUTHENTICATION_TIME,
+                TimeDuration.fromCurrent( startTime ).asMillis() );
+
+
+        log( PwmLogLevel.DEBUG, () -> "successful ldap authentication for " + userIdentity
+                + " (" +  TimeDuration.fromCurrent( startTime ).asCompactString() + ")"
+                + " type: " +  authenticationResult.getAuthenticationType() + ", using strategy " + strategy
+                + ", using proxy connection: " +  usingProxy
+                + ", returning bind dn: "
+                + ( authenticationResult.getUserProvider() == null
+                ? "none"
+                : authenticationResult.getUserProvider().getChaiConfiguration().getSetting( ChaiSetting.BIND_DN ) ) );
 
         final MacroMachine macroMachine = MacroMachine.forUser( pwmApplication, PwmConstants.DEFAULT_LOCALE, sessionLabel, userIdentity );
         final AuditRecord auditRecord = new AuditRecordFactory( pwmApplication, macroMachine ).createUserAuditRecord(
                 AuditEvent.AUTHENTICATE,
                 this.userIdentity,
-                makeAuditLogMessage( returnAuthType ),
+                makeAuditLogMessage( authenticationResult.getAuthenticationType() ),
                 sessionLabel.getSrcAddress(),
                 sessionLabel.getSrcHostname()
         );
         pwmApplication.getAuditManager().submit( auditRecord );
         pwmApplication.getSessionTrackService().addRecentLogin( userIdentity );
 
-        return authenticationResult;
+
     }
 
     private void initialize( )
@@ -345,26 +389,26 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
             final UserIdentity userIdentity,
             final PasswordData password
     )
-            throws ChaiUnavailableException, PwmUnrecoverableException, PwmOperationalException
+            throws PwmUnrecoverableException, PwmOperationalException
     {
-        log( PwmLogLevel.TRACE, "beginning testCredentials process" );
+        log( PwmLogLevel.TRACE, () -> "beginning testCredentials process" );
 
         if ( userIdentity == null || userIdentity.getUserDN() == null || userIdentity.getUserDN().length() < 1 )
         {
             final String errorMsg = "attempt to authenticate with null userDN";
-            log( PwmLogLevel.DEBUG, errorMsg );
+            log( PwmLogLevel.DEBUG, () -> errorMsg );
             throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_WRONGPASSWORD, errorMsg ) );
         }
 
         if ( password == null )
         {
             final String errorMsg = "attempt to authenticate with null password";
-            log( PwmLogLevel.DEBUG, errorMsg );
+            log( PwmLogLevel.DEBUG, () -> errorMsg );
             throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_WRONGPASSWORD, errorMsg ) );
         }
 
         //try authenticating the user using a normal ldap BIND operation.
-        log( PwmLogLevel.TRACE, "attempting authentication using ldap BIND" );
+        log( PwmLogLevel.TRACE, () -> "attempting authentication using ldap BIND" );
 
         boolean bindSucceeded = false;
         try
@@ -390,7 +434,7 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
             {
                 final String errorMsg = "intruder lockout detected for user " + userIdentity + " marking session as locked out: " + e.getMessage();
                 final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTRUDER_LDAP, errorMsg );
-                log( PwmLogLevel.WARN, errorInformation.toDebugStr() );
+                log( PwmLogLevel.WARN, () -> errorInformation.toDebugStr() );
                 throw new PwmUnrecoverableException( errorInformation );
             }
             final PwmError pwmError = PwmError.forChaiError( e.getErrorCode() );
@@ -403,7 +447,7 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
             {
                 errorInformation = new ErrorInformation( PwmError.ERROR_WRONGPASSWORD, "ldap error during password check: " + e.getMessage() );
             }
-            log( PwmLogLevel.DEBUG, errorInformation.toDebugStr() );
+            log( PwmLogLevel.DEBUG, () -> errorInformation.toDebugStr() );
             throw new PwmOperationalException( errorInformation );
         }
         finally
@@ -417,7 +461,7 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
                 }
                 catch ( Throwable e )
                 {
-                    log( PwmLogLevel.ERROR, "unexpected error closing invalid ldap connection after failed login attempt: " + e.getMessage() );
+                    log( PwmLogLevel.ERROR, () -> "unexpected error closing invalid ldap connection after failed login attempt: " + e.getMessage() );
                 }
             }
         }
@@ -426,7 +470,7 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
     private PasswordData learnUserPassword( )
             throws ChaiUnavailableException, PwmUnrecoverableException
     {
-        log( PwmLogLevel.TRACE, "beginning auth processes for user with unknown password" );
+        log( PwmLogLevel.TRACE, () -> "beginning auth processes for user with unknown password" );
         return LdapOperationsHelper.readLdapPassword( pwmApplication, sessionLabel, userIdentity );
     }
 
@@ -443,7 +487,7 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
         // try setting a random password on the account to authenticate.
         if ( !configAlwaysUseProxy && requestedAuthType == AuthenticationType.AUTH_FROM_PUBLIC_MODULE )
         {
-            log( PwmLogLevel.DEBUG, "attempting to set temporary random password" );
+            log( PwmLogLevel.DEBUG, () -> "attempting to set temporary random password" );
 
             final PwmPasswordPolicy passwordPolicy = PasswordUtility.readPasswordPolicyForUser(
                     pwmApplication,
@@ -471,12 +515,12 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
 
                 oraclePostTemporaryPwHandler( chaiProvider, chaiUser, oracleDSPrePasswordAllowChangeTime );
 
-                log( PwmLogLevel.INFO, "user " + userIdentity + " password has been set to random value to use for user authentication" );
+                log( PwmLogLevel.DEBUG, () -> "user " + userIdentity + " password has been set to random value to use for user authentication" );
             }
             catch ( ChaiOperationException e )
             {
                 final String errorStr = "error setting random password for user " + userIdentity + " " + e.getMessage();
-                log( PwmLogLevel.ERROR, errorStr );
+                log( PwmLogLevel.ERROR, () -> errorStr );
                 throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_BAD_SESSION_PASSWORD, errorStr ) );
             }
 
@@ -505,7 +549,7 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
         final String oracleDSPrePasswordAllowChangeTime = chaiProvider.readStringAttribute(
                 chaiUser.getEntryDN(),
                 ORACLE_ATTR_PW_ALLOW_CHG_TIME );
-        log( PwmLogLevel.TRACE, "read OracleDS value of passwordAllowChangeTime value=" + oracleDSPrePasswordAllowChangeTime );
+        log( PwmLogLevel.TRACE, () -> "read OracleDS value of passwordAllowChangeTime value=" + oracleDSPrePasswordAllowChangeTime );
 
 
         if ( oracleDSPrePasswordAllowChangeTime != null && !oracleDSPrePasswordAllowChangeTime.isEmpty() )
@@ -559,7 +603,7 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
             chaiProvider.writeStringAttribute( chaiUser.getEntryDN(), ORACLE_ATTR_PW_ALLOW_CHG_TIME,
                     values,
                     true );
-            log( PwmLogLevel.TRACE, "re-wrote passwordAllowChangeTime attribute to user " + chaiUser.getEntryDN() + ", value=" + oracleDSPrePasswordAllowChangeTime );
+            log( PwmLogLevel.TRACE, () -> "re-wrote passwordAllowChangeTime attribute to user " + chaiUser.getEntryDN() + ", value=" + oracleDSPrePasswordAllowChangeTime );
         }
         else
         {
@@ -571,22 +615,22 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
                 final boolean postTempUseCurrentTime = Boolean.parseBoolean( pwmApplication.getConfig().readAppProperty( AppProperty.LDAP_ORACLE_POST_TEMPPW_USE_CURRENT_TIME ) );
                 if ( postTempUseCurrentTime )
                 {
-                    log( PwmLogLevel.TRACE, "a new value for passwordAllowChangeTime attribute to user "
+                    log( PwmLogLevel.TRACE, () -> "a new value for passwordAllowChangeTime attribute to user "
                             + chaiUser.getEntryDN() + " has appeared, will replace with current time value" );
                     final String newTimeValue = OracleDSEntries.convertDateToZulu( Instant.now() );
                     final Set<String> values = new HashSet<>( Collections.singletonList( newTimeValue ) );
                     chaiProvider.writeStringAttribute( chaiUser.getEntryDN(), ORACLE_ATTR_PW_ALLOW_CHG_TIME, values, true );
-                    log( PwmLogLevel.TRACE, "wrote attribute value '" + newTimeValue + "' for passwordAllowChangeTime attribute on user "
+                    log( PwmLogLevel.TRACE, () -> "wrote attribute value '" + newTimeValue + "' for passwordAllowChangeTime attribute on user "
                             + chaiUser.getEntryDN() );
                 }
                 else
                 {
                     // password allow change time has appeared, but wasn't present previously, so delete it.
-                    log( PwmLogLevel.TRACE, "a new value for passwordAllowChangeTime attribute to user " + chaiUser.getEntryDN()
+                    log( PwmLogLevel.TRACE, () -> "a new value for passwordAllowChangeTime attribute to user " + chaiUser.getEntryDN()
                             + " has appeared, will remove" );
                     chaiProvider.deleteStringAttributeValue( chaiUser.getEntryDN(), ORACLE_ATTR_PW_ALLOW_CHG_TIME,
                             oracleDSPostPasswordAllowChangeTime );
-                    log( PwmLogLevel.TRACE, "deleted attribute value for passwordAllowChangeTime attribute on user " + chaiUser.getEntryDN() );
+                    log( PwmLogLevel.TRACE, () -> "deleted attribute value for passwordAllowChangeTime attribute on user " + chaiUser.getEntryDN() );
                 }
 
             }
@@ -619,9 +663,9 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
         return LdapOperationsHelper.createChaiProvider( pwmApplication, sessionLabel, profile, pwmApplication.getConfig(), proxyDN, proxyPassword );
     }
 
-    private void log( final PwmLogLevel level, final CharSequence message )
+    private void log( final PwmLogLevel level, final Supplier<CharSequence> message )
     {
-        LOGGER.log( level, sessionLabel, "authID=" + operationNumber + ", " + message );
+        LOGGER.log( level, sessionLabel, () -> "authID=" + operationNumber + ", " + message.get() );
     }
 
     private String makeAuditLogMessage( final AuthenticationType authenticationType )

+ 5 - 0
server/src/main/java/password/pwm/util/logging/PwmLogger.java

@@ -262,6 +262,11 @@ public class PwmLogger
         doLogEvent( level, sessionLabel, message, null );
     }
 
+    public void log( final PwmLogLevel level, final SessionLabel sessionLabel, final Supplier<CharSequence> message )
+    {
+        doLogEvent( level, sessionLabel, message, null );
+    }
+
     public void trace( final Supplier<CharSequence> message )
     {
         doLogEvent( PwmLogLevel.TRACE, null, message, null );

+ 2 - 0
server/src/main/resources/password/pwm/AppProperty.properties

@@ -38,6 +38,7 @@ audit.syslog.cef.header.severity=Unknown
 audit.syslog.cef.header.vendor=@PwmAppName@
 audit.syslog.message.length=900
 audit.syslog.message.truncateMsg=[truncated]
+auth.allowSSOwithUnknownPassword=true
 backup.path=backup
 backup.config.count=20
 backup.localdb.count=10
@@ -162,6 +163,7 @@ http.parameter.oauth.expires=expires_in
 http.parameter.oauth.responseType=response_type
 http.parameter.oauth.redirectUri=redirect_uri
 http.parameter.oauth.refreshToken=refresh_token
+http.parameter.oauth.scope=scope
 http.parameter.oauth.state=state
 http.parameter.oauth.grantType=grant_type
 http.download.buffer.size=102400

+ 4 - 0
server/src/main/resources/password/pwm/config/PwmSetting.xml

@@ -4026,6 +4026,10 @@
         <example>https://oauthserver.example.com/osp/a/idm/auth/oauth2/grant</example>
         <default/>
     </setting>
+    <setting hidden="false" key="oauth.idserver.scope" level="2">
+        <example>email</example>
+        <default/>
+    </setting>
     <setting hidden="false" key="oauth.idserver.codeResolveUrl" level="2">
         <example>https://oauthserver.example.com/osp/a/idm/auth/oauth2/authcoderesolve</example>
         <default/>

+ 4 - 2
server/src/main/resources/password/pwm/i18n/PwmSetting.properties

@@ -507,6 +507,7 @@ Setting_Description_newUser.username.definition=<p>Specify the display name, or
 Setting_Description_newUser.writeAttributes=Specify the actions the system takes when it creates a user.  The actions will be executed just after the user is created in the LDAP directory.    You can use macros in this setting.
 Setting_Description_notes.noteText=Specify any configuration notes about your system. This option allows you to keep notes about any specific configuration options you have made with the system.
 Setting_Description_oauth.idserver.attributesUrl=Specify the URL of the web service provided by the identity server to return attribute data about the user.
+Setting_Description_oauth.idserver.scope=Specify the optional OAuth scope. The OAuth identity service provider(IdP) provides this value.  The scope provided, if any, must contain the user attribute to be read for authentication.
 Setting_Description_oauth.idserver.clientName=Specify the OAuth client ID. The OAuth identity service provider(IdP) provides this value.
 Setting_Description_oauth.idserver.codeResolveUrl=Specify the OAuth Code Resolve Service URL. The system uses this web service URL to resolve the artifact returned by the OAuth identity server.
 Setting_Description_oauth.idserver.dnAttributeName=Specify the attribute to request from the OAuth server @PwmAppName@ uses as the user name for local authentication. @PwmAppName@ resolves this value the same as if the user had typed the password at the local authentication page.
@@ -634,7 +635,7 @@ Setting_Description_recovery.form=Specify the form fields for the activate user
 Setting_Description_recovery.minimumPasswordLifetimeOptions=Options to control behavior when a user attempts to use the forgotten password module while their password is within the minimum password policy lifetime window of their effective password policy.  These options are only relevant if the user has an effective minimum password lifetime as part of their password policy.
 Setting_Description_recovery.oauth.idserver.attributesUrl=Specify the web service URL provided by the identity server to return attribute data about the user.
 Setting_Description_recovery.oauth.idserver.clientName=Specify the OAuth client ID. The OAuth identity service provider gives you this value.
-Setting_Description_recovery.oauth.idserver.codeResolveUrl=Specify the OAuth Code Resolve Service URL. @PwmAppName@ uses this web service URL to resolve the artifact returned by the OAuth identity server.
+Setting_Description_recovery.oauth.idserver.codeResolveUrl=Specify the OAuth Token / Code Resolve Service URL. @PwmAppName@ uses this web service URL to resolve the artifact returned by the OAuth identity server.
 Setting_Description_recovery.oauth.idserver.dnAttributeName=Specify the attribute to request from the OAuth server that @PwmAppName@ uses as the user name for local authentication. @PwmAppName@ then resolves this value the same as if the user had typed the password at the local authentication page.
 Setting_Description_recovery.oauth.idserver.loginUrl=Specify the OAuth server login URL. @PwmAppName@ uses this is the URL to redirect the user to for authentication.
 Setting_Description_recovery.oauth.idserver.secret=Specify the OAuth shared secret. The OAuth identity service provider gives you this value.
@@ -1025,8 +1026,9 @@ Setting_Label_newUser.username.definition=LDAP Entry ID Definition
 Setting_Label_newUser.writeAttributes=New User Actions
 Setting_Label_notes.noteText=Configuration Notes
 Setting_Label_oauth.idserver.attributesUrl=OAuth Profile/UserInfo Service URL
+Setting_Label_oauth.idserver.scope=OAuth Scope
 Setting_Label_oauth.idserver.clientName=OAuth Client ID
-Setting_Label_oauth.idserver.codeResolveUrl=OAuth Code Resolve Service URL
+Setting_Label_oauth.idserver.codeResolveUrl=OAuth Token / Code Resolve Service URL
 Setting_Label_oauth.idserver.dnAttributeName=OAuth User Name/DN Login Attribute
 Setting_Label_oauth.idserver.loginUrl=OAuth Login URL
 Setting_Label_oauth.idserver.secret=OAuth Shared Secret