소스 검색

oauth parsing enhancements

jrivard@gmail.com 6 년 전
부모
커밋
7ccfe576c7

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

@@ -154,6 +154,7 @@ public enum AppProperty
     HTTP_PARAM_OAUTH_ACCESS_TOKEN                   ( "http.parameter.oauth.accessToken" ),
     HTTP_PARAM_OAUTH_ATTRIBUTES                     ( "http.parameter.oauth.attributes" ),
     HTTP_PARAM_OAUTH_CLIENT_ID                      ( "http.parameter.oauth.clientID" ),
+    HTTP_PARAM_OAUTH_CLIENT_SECRET                  ( "http.parameter.oauth.clientSecret" ),
     HTTP_PARAM_OAUTH_CODE                           ( "http.parameter.oauth.code" ),
     HTTP_PARAM_OAUTH_EXPIRES                        ( "http.parameter.oauth.expires" ),
     HTTP_PARAM_OAUTH_RESPONSE_TYPE                  ( "http.parameter.oauth.responseType" ),

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

@@ -46,7 +46,7 @@ public class OAuthFilterAuthenticationProvider implements PwmHttpFilterAuthentic
         }
 
         final String originalURL = pwmRequest.getURLwithQueryString();
-        final OAuthMachine oAuthMachine = new OAuthMachine( oauthSettings );
+        final OAuthMachine oAuthMachine = new OAuthMachine( pwmRequest.getSessionLabel(), oauthSettings );
         oAuthMachine.redirectUserToOAuthServer( pwmRequest, originalURL, null, null );
         redirected = true;
     }

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

@@ -171,7 +171,7 @@ public class AuthenticationFilter extends AbstractPwmFilter
         if ( pwmSession.getLoginInfoBean().getOauthExp() != null )
         {
             final OAuthSettings oauthSettings = OAuthSettings.forSSOAuthentication( pwmRequest.getConfig() );
-            final OAuthMachine oAuthMachine = new OAuthMachine( oauthSettings );
+            final OAuthMachine oAuthMachine = new OAuthMachine( pwmRequest.getSessionLabel(), oauthSettings );
             if ( oAuthMachine.checkOAuthExpiration( pwmRequest ) )
             {
                 pwmRequest.respondWithError( new ErrorInformation( PwmError.ERROR_OAUTH_ERROR, "oauth access token has expired" ) );

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java

@@ -1501,7 +1501,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
                 forgottenPasswordBean.getProgress().setInProgressVerificationMethod( IdentityVerificationMethod.OAUTH );
                 final ForgottenPasswordProfile forgottenPasswordProfile = ForgottenPasswordUtil.forgottenPasswordProfile( pwmRequest.getPwmApplication(), forgottenPasswordBean );
                 final OAuthSettings oAuthSettings = OAuthSettings.forForgottenPassword( forgottenPasswordProfile );
-                final OAuthMachine oAuthMachine = new OAuthMachine( oAuthSettings );
+                final OAuthMachine oAuthMachine = new OAuthMachine( pwmRequest.getSessionLabel(), oAuthSettings );
                 pwmRequest.getPwmApplication().getSessionStateService().saveSessionBeans( pwmRequest );
                 final UserIdentity userIdentity = forgottenPasswordBean.getUserIdentity();
                 oAuthMachine.redirectUserToOAuthServer( pwmRequest, null, userIdentity, forgottenPasswordProfile.getIdentifier() );

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

@@ -163,7 +163,7 @@ public class OAuthConsumerServlet extends AbstractPwmServlet
 
         final OAuthState oauthState = oAuthRequestState.get().getOAuthState();
         final OAuthSettings oAuthSettings = makeOAuthSettings( pwmRequest, oauthState );
-        final OAuthMachine oAuthMachine = new OAuthMachine( oAuthSettings );
+        final OAuthMachine oAuthMachine = new OAuthMachine( pwmRequest.getSessionLabel(), oAuthSettings );
 
         // make sure request was initiated in users current session
         if ( !oAuthRequestState.get().isSessionMatch() )

+ 91 - 51
server/src/main/java/password/pwm/http/servlet/oauth/OAuthMachine.java

@@ -56,6 +56,7 @@ import java.net.URISyntaxException;
 import java.security.cert.X509Certificate;
 import java.time.Instant;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -66,10 +67,15 @@ public class OAuthMachine
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( OAuthMachine.class );
 
+    private final SessionLabel sessionLabel;
     private final OAuthSettings settings;
 
-    public OAuthMachine( final OAuthSettings settings )
+    public OAuthMachine(
+            final SessionLabel sessionLabel,
+            final OAuthSettings settings
+    )
     {
+        this.sessionLabel = sessionLabel;
         this.settings = settings;
     }
 
@@ -104,7 +110,7 @@ public class OAuthMachine
             throws PwmUnrecoverableException, IOException
     {
 
-        LOGGER.trace( pwmRequest, () -> "preparing to redirect user to oauth authentication service, setting nextUrl to " + nextUrl );
+        LOGGER.trace( sessionLabel, () -> "preparing to redirect user to oauth authentication service, setting nextUrl to " + nextUrl );
         pwmRequest.getPwmSession().getSessionStateBean().setOauthInProgress( true );
 
         final Configuration config = pwmRequest.getConfig();
@@ -138,7 +144,7 @@ public class OAuthMachine
         {
             pwmRequest.sendRedirect( redirectUrl );
             pwmRequest.getPwmSession().getSessionStateBean().setOauthInProgress( true );
-            LOGGER.debug( pwmRequest, () -> "redirecting user to oauth id server, url: " + redirectUrl );
+            LOGGER.debug( sessionLabel, () -> "redirecting user to oauth id server, url: " + redirectUrl );
         }
         catch ( PwmUnrecoverableException e )
         {
@@ -165,36 +171,34 @@ public class OAuthMachine
         requestParams.put( config.readAppProperty( AppProperty.HTTP_PARAM_OAUTH_GRANT_TYPE ), grantType );
         requestParams.put( config.readAppProperty( AppProperty.HTTP_PARAM_OAUTH_REDIRECT_URI ), redirectUri );
         requestParams.put( config.readAppProperty( AppProperty.HTTP_PARAM_OAUTH_CLIENT_ID ), clientID );
-        requestParams.put( "client_secret", settings.getSecret().getStringValue() );
+        requestParams.put( config.readAppProperty( AppProperty.HTTP_PARAM_OAUTH_CLIENT_SECRET ), settings.getSecret().getStringValue() );
 
         final PwmHttpClientResponse restResults = makeHttpRequest( pwmRequest, "oauth code resolver", settings, requestUrl, requestParams, null );
 
-        return resolveResultsFromResponseBody( pwmRequest.getSessionLabel(), pwmRequest.getConfig(), restResults.getBody() );
+        final OAuthResolveResults results = resolveResultsFromResponseBody( pwmRequest, restResults.getBody() );
+
+        LOGGER.trace( sessionLabel, () -> "successfully received access token" );
+
+        return results;
     }
 
     private OAuthResolveResults resolveResultsFromResponseBody(
-            final SessionLabel sessionLabel,
-            final Configuration config,
+            final PwmRequest pwmRequest,
             final String resolveResponseBodyStr
-            )
+    )
     {
-        final Map<String, String> resolveResultValues = JsonUtil.deserializeStringMap( resolveResponseBodyStr );
-
-        int expireSeconds = 0;
-        try
-        {
-            expireSeconds = Integer.parseInt( resolveResultValues.get( config.readAppProperty( AppProperty.HTTP_PARAM_OAUTH_EXPIRES ) ) );
-        }
-        catch ( Exception e )
-        {
-            LOGGER.warn( sessionLabel, "error parsing oauth expires value in code resolver response from server, error: " + e.getMessage() );
-        }
+        final Configuration config = pwmRequest.getConfig();
+        final String oauthExpiresParam = config.readAppProperty( AppProperty.HTTP_PARAM_OAUTH_EXPIRES );
+        final String oauthAccessTokenParam = config.readAppProperty( AppProperty.HTTP_PARAM_OAUTH_ACCESS_TOKEN );
+        final String refreshTokenParam = config.readAppProperty( AppProperty.HTTP_PARAM_OAUTH_REFRESH_TOKEN );
 
-        final String accessToken = readAttributeFromBodyMap( resolveResultValues, config.readAppProperty( AppProperty.HTTP_PARAM_OAUTH_ACCESS_TOKEN ) );
+        final long expireSeconds = JavaHelper.silentParseLong( readAttributeFromBodyMap( resolveResponseBodyStr, oauthExpiresParam ), 0 );
+        final String accessToken = readAttributeFromBodyMap( resolveResponseBodyStr, oauthAccessTokenParam );
+        final String refreshToken = readAttributeFromBodyMap( resolveResponseBodyStr, refreshTokenParam );
 
         return OAuthResolveResults.builder()
                 .accessToken( accessToken )
-                .refreshToken( resolveResultValues.get( config.readAppProperty( AppProperty.HTTP_PARAM_OAUTH_REFRESH_TOKEN ) ) )
+                .refreshToken( refreshToken  )
                 .expiresSeconds( expireSeconds )
                 .build();
     }
@@ -215,7 +219,7 @@ public class OAuthMachine
 
         final PwmHttpClientResponse restResults = makeHttpRequest( pwmRequest, "OAuth refresh resolver", settings, requestUrl, requestParams, null );
 
-        return resolveResultsFromResponseBody( pwmRequest.getSessionLabel(), pwmRequest.getConfig(), restResults.getBody() );
+        return resolveResultsFromResponseBody( pwmRequest, restResults.getBody() );
     }
 
     String makeOAuthGetUserInfoRequest(
@@ -224,31 +228,31 @@ public class OAuthMachine
     )
             throws PwmUnrecoverableException
     {
-        final Configuration config = pwmRequest.getConfig();
-        final String requestUrl = settings.getAttributesUrl();
-        final Map<String, String> requestParams = new HashMap<>();
-        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 userinfo", settings, requestUrl, requestParams, accessToken );
+        final PwmHttpClientResponse restResults;
+        {
+            final Configuration config = pwmRequest.getConfig();
+            final String requestUrl = settings.getAttributesUrl();
+            final Map<String, String> requestParams = new HashMap<>();
+            requestParams.put( config.readAppProperty( AppProperty.HTTP_PARAM_OAUTH_ACCESS_TOKEN ), accessToken );
+            requestParams.put( config.readAppProperty( AppProperty.HTTP_PARAM_OAUTH_ATTRIBUTES ), settings.getDnAttributeName() );
+            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() ) );
+        LOGGER.trace( sessionLabel, () -> "received attribute values from OAuth IdP for attributes: " );
 
-        final String oauthSuppliedUsername = getAttributeResultValues.get( settings.getDnAttributeName() );
+        final String oauthSuppliedUsername = readAttributeFromBodyMap( resultBody, settings.getDnAttributeName() );
 
         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 );
+            LOGGER.error( sessionLabel, errorInformation );
             throw new PwmUnrecoverableException( errorInformation );
         }
 
-        LOGGER.debug( pwmRequest, () -> "received user login id value from OAuth server: " + oauthSuppliedUsername );
+        LOGGER.debug( sessionLabel, () -> "received user login id value from OAuth server: " + oauthSuppliedUsername );
 
         return oauthSuppliedUsername;
     }
@@ -352,7 +356,7 @@ public class OAuthMachine
             }
         }
 
-        LOGGER.trace( () -> "calculated oauth self end point URI as '" + redirectUri + "' using method " + debugSource );
+        LOGGER.trace( pwmRequest, () -> "calculated oauth self end point URI as '" + redirectUri + "' using method " + debugSource );
         return redirectUri;
     }
 
@@ -374,7 +378,7 @@ public class OAuthMachine
             return false;
         }
 
-        LOGGER.trace( pwmRequest, () -> "oauth access token has expired, attempting to refresh" );
+        LOGGER.trace( sessionLabel, () -> "oauth access token has expired, attempting to refresh" );
 
         try
         {
@@ -385,7 +389,7 @@ public class OAuthMachine
                 if ( resolveResults.getExpiresSeconds() > 0 )
                 {
                     final Instant accessTokenExpirationDate = Instant.ofEpochMilli( System.currentTimeMillis() + 1000 * resolveResults.getExpiresSeconds() );
-                    LOGGER.trace( pwmRequest, () -> "noted oauth access token expiration at timestamp " + JavaHelper.toIsoDate( accessTokenExpirationDate ) );
+                    LOGGER.trace( sessionLabel, () -> "noted oauth access token expiration at timestamp " + JavaHelper.toIsoDate( accessTokenExpirationDate ) );
                     loginInfoBean.setOauthExp( accessTokenExpirationDate );
                     loginInfoBean.setOauthRefToken( resolveResults.getRefreshToken() );
                     return false;
@@ -394,9 +398,9 @@ public class OAuthMachine
         }
         catch ( PwmUnrecoverableException e )
         {
-            LOGGER.error( pwmRequest, "error while processing oauth token refresh: " + e.getMessage() );
+            LOGGER.error( sessionLabel, "error while processing oauth token refresh: " + e.getMessage() );
         }
-        LOGGER.error( pwmRequest, "unable to refresh oauth token for user, unauthenticated session" );
+        LOGGER.error( sessionLabel, "unable to refresh oauth token for user, unauthenticated session" );
         pwmRequest.getPwmSession().unauthenticateUser( pwmRequest );
         return true;
     }
@@ -428,7 +432,7 @@ public class OAuthMachine
                 throw new IllegalStateException( "unexpected oAuthUseCase: " + oAuthUseCase );
         }
 
-        LOGGER.trace( pwmRequest, () -> "issuing oauth state id="
+        LOGGER.trace( sessionLabel, () -> "issuing oauth state id="
                 + oAuthState.getStateID() + " with the next destination URL set to " + oAuthState.getNextUrl() );
 
 
@@ -455,7 +459,7 @@ public class OAuthMachine
 
         final MacroMachine macroMachine = MacroMachine.forUser( pwmRequest, userIdentity );
         final String username = macroMachine.expandMacros( macroText );
-        LOGGER.debug( pwmRequest, () -> "calculated username value for user as: " + username );
+        LOGGER.debug( sessionLabel, () -> "calculated username value for user as: " + username );
 
         final String grantUrl = settings.getLoginURL();
         final String signUrl = grantUrl.replace( "/grant", "/sign" );
@@ -472,28 +476,64 @@ public class OAuthMachine
             requestPayload.put( "data", JsonUtil.serializeCollection( listWrapper ) );
         }
 
-        LOGGER.debug( pwmRequest, () -> "preparing to send username to OAuth /sign endpoint for future injection to /grant redirect" );
+        LOGGER.debug( sessionLabel, () -> "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, null );
 
         final String resultBody = restResults.getBody();
         final Map<String, String> resultBodyMap = JsonUtil.deserializeStringMap( resultBody );
         final String data = resultBodyMap.get( "data" );
-        LOGGER.debug( pwmRequest, () -> "oauth /sign endpoint returned signed username data: " + data );
+        LOGGER.debug( sessionLabel, () -> "oauth /sign endpoint returned signed username data: " + data );
         return data;
     }
 
-    private static String readAttributeFromBodyMap( final Map<String, String> bodyMap, final String attributeNames )
+    public String readAttributeFromBodyMap(
+            final String bodyString,
+            final String attributeNames
+    )
     {
-        final List<String> attributeValues = StringUtil.splitAndTrim( attributeNames, "," );
-
-        for ( final String attribute : attributeValues )
+        try
         {
-            final String value = bodyMap.get( attribute );
-            if ( !StringUtil.isEmpty( value ) )
+            final Map<String, Object> bodyMap = JsonUtil.deserializeMap( bodyString );
+            final List<String> attributeValues = StringUtil.splitAndTrim( attributeNames, "," );
+
+            for ( final String attribute : attributeValues )
             {
-                return value;
+                final Object objValue = bodyMap.get( attribute );
+                if ( objValue != null )
+                {
+                    if ( objValue instanceof Double && JavaHelper.doubleContainsLongValue( (Double) objValue ) )
+                    {
+                        final long longValue = ( ( Double ) objValue ).longValue();
+                        return Long.toString( longValue );
+                    }
+
+                    final Object singleObjValue;
+                    if ( objValue instanceof Collection )
+                    {
+                        if ( ( ( Collection ) objValue ).isEmpty() )
+                        {
+                            return null;
+                        }
+
+                        singleObjValue = ( ( Collection ) objValue ).iterator().next();
+                    }
+                    else
+                    {
+                        singleObjValue = objValue;
+                    }
+
+                    final String strValue = singleObjValue.toString();
+                    if ( !StringUtil.isEmpty( strValue ) )
+                    {
+                        return strValue;
+                    }
+                }
             }
         }
+        catch ( Exception e )
+        {
+            LOGGER.debug( sessionLabel, () -> "unexpected error parsing json response: " + e.getMessage() );
+        }
 
         return null;
     }

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/oauth/OAuthResolveResults.java

@@ -32,6 +32,6 @@ import java.io.Serializable;
 class OAuthResolveResults implements Serializable
 {
     private String accessToken;
-    private int expiresSeconds;
+    private long expiresSeconds;
     private String refreshToken;
 }

+ 33 - 0
server/src/main/java/password/pwm/util/java/JavaHelper.java

@@ -737,4 +737,37 @@ public class JavaHelper
                     .collect( Collectors.toCollection( LinkedHashSet::new ) );
         }
     }
+
+    public static int silentParseInt( final String input, final int defaultValue )
+    {
+        try
+        {
+            return Integer.parseInt( input );
+        }
+        catch ( NumberFormatException e )
+        {
+            return defaultValue;
+        }
+    }
+
+    public static long silentParseLong( final String input, final long defaultValue )
+    {
+        try
+        {
+            return Long.parseLong( input );
+        }
+        catch ( NumberFormatException e )
+        {
+            return defaultValue;
+        }
+    }
+
+    public static boolean doubleContainsLongValue( final Double input )
+    {
+        return input.equals( Math.floor( input ) )
+                && !Double.isInfinite( input )
+                && !Double.isNaN( input )
+                && input <= Long.MAX_VALUE
+                && input >= Long.MIN_VALUE;
+    }
 }

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

@@ -159,6 +159,7 @@ http.parameter.ssoOverride=sso
 http.parameter.oauth.accessToken=access_token
 http.parameter.oauth.attributes=attributes
 http.parameter.oauth.clientID=client_id
+http.parameter.oauth.clientSecret=client_secret
 http.parameter.oauth.code=code
 http.parameter.oauth.expires=expires_in
 http.parameter.oauth.responseType=response_type

+ 62 - 0
server/src/test/java/password/pwm/http/servlet/oauth/OAuthMachineTest.java

@@ -0,0 +1,62 @@
+/*
+ * 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.servlet.oauth;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class OAuthMachineTest
+{
+    @Test
+    public void parserTest1()
+    {
+        final String input = "{\n"
+                + "\t\"access_token\":\"Q6hgBgSZMMvVnOP2tOTufILVfao82kcHtVqE9pspzC55oqKdMjuaz9Jpj3KpTlv\",\n"
+                + "\t\"token_type\":\"bearer\",\n"
+                + "\t\"expires_in\":3599,\n"
+                + "\t\"scope\":\"profile\"\n"
+                + "}";
+        final OAuthSettings oAuthSettings = OAuthSettings.builder().build();
+        final OAuthMachine oAuthMachine = new OAuthMachine( null, oAuthSettings );
+        Assert.assertEquals( "3599", oAuthMachine.readAttributeFromBodyMap( input, "expires_in" ) );
+    }
+
+    @Test
+    public void parserTest2()
+    {
+        final String input = "{\"sub\":\"0c8463c904e6444fa5c2b4597f816bc2\",\"claims\":[],\"email\":\"testadmin@example.com\"}";
+        final OAuthSettings oAuthSettings = OAuthSettings.builder().build();
+        final OAuthMachine oAuthMachine = new OAuthMachine( null, oAuthSettings );
+        Assert.assertEquals( "testadmin@example.com", oAuthMachine.readAttributeFromBodyMap( input, "email" ) );
+        Assert.assertNull( oAuthMachine.readAttributeFromBodyMap( input, "claims" ) );
+    }
+
+    @Test
+    public void parserTest3()
+    {
+        final String input = "{\"sub\":\"0c8463c904e6444fa5c2b4597f816bc2\",\"claims\":[\"value1\",\"value2\"],\"email\":\"testadmin@example.com\"}";
+        final OAuthSettings oAuthSettings = OAuthSettings.builder().build();
+        final OAuthMachine oAuthMachine = new OAuthMachine( null, oAuthSettings );
+        Assert.assertEquals( "value1", oAuthMachine.readAttributeFromBodyMap( input, "claims" ) );
+    }
+}